nostr 0.4.0 โ 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.adr-dir +1 -0
- data/.editorconfig +1 -1
- data/.rubocop.yml +24 -1
- data/.tool-versions +2 -1
- data/CHANGELOG.md +70 -1
- data/README.md +93 -228
- data/adr/0001-record-architecture-decisions.md +19 -0
- data/adr/0002-introduction-of-signature-class.md +27 -0
- data/docs/.gitignore +4 -0
- data/docs/.vitepress/config.mjs +114 -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/common-use-cases/signing-and-verifying-events.md +50 -0
- data/docs/common-use-cases/signing-and-verifying-messages.md +43 -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 +171 -0
- data/docs/implemented-nips.md +9 -0
- data/docs/index.md +42 -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 +93 -13
- 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/invalid_signature_format_error.rb +18 -0
- data/lib/nostr/errors/invalid_signature_length_error.rb +18 -0
- data/lib/nostr/errors/invalid_signature_type_error.rb +16 -0
- data/lib/nostr/errors/key_validation_error.rb +6 -0
- data/lib/nostr/errors/signature_validation_error.rb +6 -0
- data/lib/nostr/errors.rb +12 -0
- data/lib/nostr/event.rb +40 -13
- data/lib/nostr/event_kind.rb +1 -0
- data/lib/nostr/events/encrypted_direct_message.rb +8 -7
- data/lib/nostr/filter.rb +14 -11
- data/lib/nostr/key.rb +100 -0
- data/lib/nostr/key_pair.rb +54 -6
- data/lib/nostr/keygen.rb +44 -5
- 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/signature.rb +67 -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 +7 -0
- data/nostr.gemspec +13 -13
- data/sig/nostr/bech32.rbs +14 -0
- data/sig/nostr/client.rbs +5 -5
- data/sig/nostr/crypto.rbs +8 -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/invalid_signature_format_error.rbs +5 -0
- data/sig/nostr/errors/invalid_signature_length_error.rbs +5 -0
- data/sig/nostr/errors/invalid_signature_type_error.rbs +5 -0
- data/sig/nostr/errors/key_validation_error.rbs +4 -0
- data/sig/nostr/errors/signature_validation_error.rbs +4 -0
- data/sig/nostr/event.rbs +11 -10
- 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 +8 -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/signature.rbs +14 -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
- data/sig/vendor/schnorr/signature.rbs +16 -0
- data/sig/vendor/schnorr.rbs +3 -1
- metadata +102 -28
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) |
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Installation
|
2
|
+
|
3
|
+
Install the gem and add to the application's Gemfile by executing:
|
4
|
+
|
5
|
+
```shell
|
6
|
+
bundle add nostr
|
7
|
+
```
|
8
|
+
|
9
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
10
|
+
|
11
|
+
```shell
|
12
|
+
gem install nostr
|
13
|
+
```
|
14
|
+
|
15
|
+
## Requiring the gem
|
16
|
+
|
17
|
+
All examples in this guide assume that the gem has been required:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
require 'nostr'
|
21
|
+
```
|
@@ -0,0 +1,171 @@
|
|
1
|
+
---
|
2
|
+
editLink: true
|
3
|
+
---
|
4
|
+
|
5
|
+
# Getting started
|
6
|
+
|
7
|
+
This gem abstracts the complexity that you would face when trying to connect to relays web sockets, send and receive
|
8
|
+
events, handle events callbacks and much more.
|
9
|
+
|
10
|
+
## Visual overview
|
11
|
+
|
12
|
+
Begin your journey with an overview of the essential functions. A visual representation below maps out the key
|
13
|
+
components we'll delve into in this section.
|
14
|
+
|
15
|
+
```mermaid
|
16
|
+
classDiagram
|
17
|
+
class Client {
|
18
|
+
connect(relay)
|
19
|
+
publish(event)
|
20
|
+
subscribe(subscription_id, filter)
|
21
|
+
unsubscribe(subscription_id)
|
22
|
+
}
|
23
|
+
class Relay {
|
24
|
+
url
|
25
|
+
name
|
26
|
+
}
|
27
|
+
class Event {
|
28
|
+
pubkey
|
29
|
+
created_at
|
30
|
+
kind
|
31
|
+
tags
|
32
|
+
content
|
33
|
+
add_event_reference(event_id)
|
34
|
+
add_pubkey_reference(pubkey)
|
35
|
+
serialize()
|
36
|
+
to_h()
|
37
|
+
sign(private_key)
|
38
|
+
verify_signature()
|
39
|
+
}
|
40
|
+
class Subscription {
|
41
|
+
id
|
42
|
+
filter
|
43
|
+
}
|
44
|
+
class Filter {
|
45
|
+
ids
|
46
|
+
authors
|
47
|
+
kinds
|
48
|
+
since
|
49
|
+
until
|
50
|
+
limit
|
51
|
+
to_h()
|
52
|
+
}
|
53
|
+
class EventKind {
|
54
|
+
<<Enumeration>>
|
55
|
+
SET_METADATA
|
56
|
+
TEXT_NOTE
|
57
|
+
RECOMMEND_SERVER
|
58
|
+
CONTACT_LIST
|
59
|
+
ENCRYPTED_DIRECT_MESSAGE
|
60
|
+
}
|
61
|
+
class KeyPair {
|
62
|
+
private_key
|
63
|
+
public_key
|
64
|
+
}
|
65
|
+
class Keygen {
|
66
|
+
generate_key_pair()
|
67
|
+
generate_private_key()
|
68
|
+
extract_public_key(private_key)
|
69
|
+
}
|
70
|
+
class User {
|
71
|
+
keypair
|
72
|
+
create_event(event_attributes)
|
73
|
+
}
|
74
|
+
|
75
|
+
Client --> Relay : connects via <br> WebSockets to
|
76
|
+
Client --> Event : uses WebSockets to <br> publish and receive
|
77
|
+
Client --> Subscription : receives Events via
|
78
|
+
Subscription --> Filter : uses
|
79
|
+
Event --> EventKind : is of kind
|
80
|
+
User --> KeyPair : has
|
81
|
+
User --> Event : creates and signs
|
82
|
+
User --> Keygen : uses to generate keys
|
83
|
+
Keygen --> KeyPair : generates
|
84
|
+
```
|
85
|
+
|
86
|
+
## Code overview
|
87
|
+
|
88
|
+
Explore the provided code snippet to learn about initializing the Nostr [client](../core/client.md), generating
|
89
|
+
a [keypair](../core/keys), [publishing](../relays/publishing-events) an event, and
|
90
|
+
efficiently [managing event subscriptions](../subscriptions/creating-a-subscription) (including event reception,
|
91
|
+
filtering, and WebSocket event handling).
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
# Require the gem
|
95
|
+
require 'nostr'
|
96
|
+
|
97
|
+
# Instantiate a client
|
98
|
+
client = Nostr::Client.new
|
99
|
+
|
100
|
+
# a) Use an existing keypair
|
101
|
+
keypair = Nostr::KeyPair.new(
|
102
|
+
private_key: Nostr::PrivateKey.new('your-hex-private-key'),
|
103
|
+
public_key: Nostr::PublicKey.new('your-hex-public-key'),
|
104
|
+
)
|
105
|
+
|
106
|
+
# b) Or build a keypair from a private key
|
107
|
+
keygen = Nostr::Keygen.new
|
108
|
+
keypair = keygen.get_key_pair_from_private_key(
|
109
|
+
Nostr::PrivateKey.new('your-hex-private-key')
|
110
|
+
)
|
111
|
+
|
112
|
+
# c) Or create a new keypair
|
113
|
+
keygen = Nostr::Keygen.new
|
114
|
+
keypair = keygen.generate_key_pair
|
115
|
+
|
116
|
+
# Create a user with the keypair
|
117
|
+
user = Nostr::User.new(keypair: keypair)
|
118
|
+
|
119
|
+
# Create a signed event
|
120
|
+
text_note_event = user.create_event(
|
121
|
+
kind: Nostr::EventKind::TEXT_NOTE,
|
122
|
+
content: 'Your feedback is appreciated, now pay $8'
|
123
|
+
)
|
124
|
+
|
125
|
+
# Connect asynchronously to a relay
|
126
|
+
relay = Nostr::Relay.new(url: 'wss://nostr.wine', name: 'Wine')
|
127
|
+
client.connect(relay)
|
128
|
+
|
129
|
+
# Listen asynchronously for the connect event
|
130
|
+
client.on :connect do
|
131
|
+
# Send the event to the Relay
|
132
|
+
client.publish(text_note_event)
|
133
|
+
|
134
|
+
# Create a filter to receive the first 20 text notes
|
135
|
+
# and encrypted direct messages from the relay that
|
136
|
+
# were created in the previous hour
|
137
|
+
filter = Nostr::Filter.new(
|
138
|
+
kinds: [
|
139
|
+
Nostr::EventKind::TEXT_NOTE,
|
140
|
+
Nostr::EventKind::ENCRYPTED_DIRECT_MESSAGE
|
141
|
+
],
|
142
|
+
since: Time.now.to_i - 3600, # 1 hour ago
|
143
|
+
until: Time.now.to_i,
|
144
|
+
limit: 20,
|
145
|
+
)
|
146
|
+
|
147
|
+
# Subscribe to events matching conditions of a filter
|
148
|
+
subscription = client.subscribe(filter: filter)
|
149
|
+
|
150
|
+
# Unsubscribe from events matching the filter above
|
151
|
+
client.unsubscribe(subscription.id)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Listen for incoming messages and print them
|
155
|
+
client.on :message do |message|
|
156
|
+
puts message
|
157
|
+
end
|
158
|
+
|
159
|
+
# Listen for error messages
|
160
|
+
client.on :error do |error_message|
|
161
|
+
# Handle the error
|
162
|
+
end
|
163
|
+
|
164
|
+
# Listen for the close event
|
165
|
+
client.on :close do |code, reason|
|
166
|
+
# You may attempt to reconnect to the relay here
|
167
|
+
end
|
168
|
+
```
|
169
|
+
|
170
|
+
Beyond what's covered here, the Nostr protocol and this gem boast a wealth of additional functionalities. For an
|
171
|
+
in-depth exploration of these capabilities, proceed to the next page.
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# Implemented NIPs
|
2
|
+
|
3
|
+
NIPs stand for Nostr Implementation Possibilities. They exist to document what may be implemented by Nostr-compatible
|
4
|
+
relay and client software.
|
5
|
+
|
6
|
+
- [NIP-01: Basic protocol flow description](https://github.com/nostr-protocol/nips/blob/master/01.md)
|
7
|
+
- [NIP-02: Contact List and Petnames](https://github.com/nostr-protocol/nips/blob/master/02.md)
|
8
|
+
- [NIP-04: Encrypted Direct Message](https://github.com/nostr-protocol/nips/blob/master/04.md)
|
9
|
+
- [NIP-19: Bech32-encoded entities](https://github.com/nostr-protocol/nips/blob/master/19.md)
|
data/docs/index.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
---
|
2
|
+
# https://vitepress.dev/reference/default-theme-home-page
|
3
|
+
layout: home
|
4
|
+
|
5
|
+
hero:
|
6
|
+
name: "Nostr"
|
7
|
+
text: "Ruby"
|
8
|
+
tagline: "The Nostr protocol implemented in a Ruby gem."
|
9
|
+
actions:
|
10
|
+
- theme: brand
|
11
|
+
text: Getting Started
|
12
|
+
link: /getting-started/overview
|
13
|
+
- theme: alt
|
14
|
+
text: View on Github
|
15
|
+
link: https://github.com/wilsonsilva/nostr
|
16
|
+
- theme: alt
|
17
|
+
text: View on RubyDoc
|
18
|
+
link: https://www.rubydoc.info/gems/nostr
|
19
|
+
- theme: alt
|
20
|
+
text: View on RubyGems
|
21
|
+
link: https://rubygems.org/gems/nostr
|
22
|
+
|
23
|
+
features:
|
24
|
+
- title: Asynchronous
|
25
|
+
details: Non-blocking I/O for maximum performance.
|
26
|
+
icon: โก
|
27
|
+
- title: Lightweight
|
28
|
+
details: Minimal runtime dependencies.
|
29
|
+
icon: ๐ชถ
|
30
|
+
- title: Intuitive
|
31
|
+
details: The API mirrors the Nostr protocol specification domain language.
|
32
|
+
icon: ๐ก
|
33
|
+
- title: Fully documented
|
34
|
+
details: All code is documented from both the maintainer's as well as the consumer's perspective.
|
35
|
+
icon: ๐
|
36
|
+
- title: Fully tested
|
37
|
+
details: All code is tested with 100% coverage.
|
38
|
+
icon: ๐งช
|
39
|
+
- title: Fully typed
|
40
|
+
details: All code is typed with <a href="https://rubygems.org/gems/rbs" target="_blank">RBS</a> with the help of <a href="https://rubygems.org/gems/typeprof" target="_blank">TypeProf</a>. Type correctness is enforced by <a href="https://rubygems.org/gems/steep" target="_blank">Steep</a>.
|
41
|
+
icon: โ
|
42
|
+
---
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# Markdown Extension Examples
|
2
|
+
|
3
|
+
This page demonstrates some of the built-in markdown extensions provided by VitePress.
|
4
|
+
|
5
|
+
## Syntax Highlighting
|
6
|
+
|
7
|
+
VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting:
|
8
|
+
|
9
|
+
**Input**
|
10
|
+
|
11
|
+
````
|
12
|
+
```js{4}
|
13
|
+
export default {
|
14
|
+
data () {
|
15
|
+
return {
|
16
|
+
msg: 'Highlighted!'
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
```
|
21
|
+
````
|
22
|
+
|
23
|
+
**Output**
|
24
|
+
|
25
|
+
```js{4}
|
26
|
+
export default {
|
27
|
+
data () {
|
28
|
+
return {
|
29
|
+
msg: 'Highlighted!'
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
33
|
+
```
|
34
|
+
|
35
|
+
## Custom Containers
|
36
|
+
|
37
|
+
**Input**
|
38
|
+
|
39
|
+
```md
|
40
|
+
::: info
|
41
|
+
This is an info box.
|
42
|
+
:::
|
43
|
+
|
44
|
+
::: tip
|
45
|
+
This is a tip.
|
46
|
+
:::
|
47
|
+
|
48
|
+
::: warning
|
49
|
+
This is a warning.
|
50
|
+
:::
|
51
|
+
|
52
|
+
::: danger
|
53
|
+
This is a dangerous warning.
|
54
|
+
:::
|
55
|
+
|
56
|
+
::: details
|
57
|
+
This is a details block.
|
58
|
+
:::
|
59
|
+
```
|
60
|
+
|
61
|
+
**Output**
|
62
|
+
|
63
|
+
::: info
|
64
|
+
This is an info box.
|
65
|
+
:::
|
66
|
+
|
67
|
+
::: tip
|
68
|
+
This is a tip.
|
69
|
+
:::
|
70
|
+
|
71
|
+
::: warning
|
72
|
+
This is a warning.
|
73
|
+
:::
|
74
|
+
|
75
|
+
::: danger
|
76
|
+
This is a dangerous warning.
|
77
|
+
:::
|
78
|
+
|
79
|
+
::: details
|
80
|
+
This is a details block.
|
81
|
+
:::
|
82
|
+
|
83
|
+
## More
|
84
|
+
|
85
|
+
Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown).
|
data/docs/package.json
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Connecting to a Relay
|
2
|
+
|
3
|
+
You must connect your nostr [Client](../core/client) to a relay in order to send and receive [Events](../events).
|
4
|
+
Instantiate a [`Nostr::Client`](https://www.rubydoc.info/gems/nostr/Nostr/Client) and a
|
5
|
+
[`Nostr::Relay`](https://www.rubydoc.info/gems/nostr/Nostr/Relay) giving it the `url` of your relay. The `name`
|
6
|
+
attribute is just descriptive.
|
7
|
+
Calling [`Client#connect`](https://www.rubydoc.info/gems/nostr/Nostr/Client#connect-instance_method) attempts to
|
8
|
+
establish a WebSocket connection between the Client and the Relay.
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
client = Nostr::Client.new
|
12
|
+
relay = Nostr::Relay.new(url: 'wss://relay.damus.io', name: 'Damus')
|
13
|
+
|
14
|
+
# Listen for the connect event
|
15
|
+
client.on :connect do
|
16
|
+
# When this block executes, you're connected to the relay
|
17
|
+
end
|
18
|
+
|
19
|
+
# Connect to a relay asynchronously
|
20
|
+
client.connect(relay)
|
21
|
+
```
|