nostr 0.4.0 โ 0.6.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/.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
|
+
```
|