nostr 0.4.0 โ†’ 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.adr-dir +1 -0
  3. data/.editorconfig +1 -1
  4. data/.rubocop.yml +24 -1
  5. data/.tool-versions +2 -1
  6. data/CHANGELOG.md +70 -1
  7. data/README.md +93 -228
  8. data/adr/0001-record-architecture-decisions.md +19 -0
  9. data/adr/0002-introduction-of-signature-class.md +27 -0
  10. data/docs/.gitignore +4 -0
  11. data/docs/.vitepress/config.mjs +114 -0
  12. data/docs/README.md +44 -0
  13. data/docs/api-examples.md +49 -0
  14. data/docs/bun.lockb +0 -0
  15. data/docs/common-use-cases/bech32-encoding-and-decoding-(NIP-19).md +190 -0
  16. data/docs/common-use-cases/signing-and-verifying-events.md +50 -0
  17. data/docs/common-use-cases/signing-and-verifying-messages.md +43 -0
  18. data/docs/core/client.md +108 -0
  19. data/docs/core/keys.md +136 -0
  20. data/docs/core/user.md +43 -0
  21. data/docs/events/contact-list.md +29 -0
  22. data/docs/events/encrypted-direct-message.md +28 -0
  23. data/docs/events/recommend-server.md +32 -0
  24. data/docs/events/set-metadata.md +20 -0
  25. data/docs/events/text-note.md +15 -0
  26. data/docs/events.md +11 -0
  27. data/docs/getting-started/installation.md +21 -0
  28. data/docs/getting-started/overview.md +171 -0
  29. data/docs/implemented-nips.md +9 -0
  30. data/docs/index.md +42 -0
  31. data/docs/markdown-examples.md +85 -0
  32. data/docs/package.json +12 -0
  33. data/docs/relays/connecting-to-a-relay.md +21 -0
  34. data/docs/relays/publishing-events.md +29 -0
  35. data/docs/relays/receiving-events.md +6 -0
  36. data/docs/subscriptions/creating-a-subscription.md +49 -0
  37. data/docs/subscriptions/deleting-a-subscription.md +10 -0
  38. data/docs/subscriptions/filtering-subscription-events.md +115 -0
  39. data/docs/subscriptions/updating-a-subscription.md +4 -0
  40. data/lib/nostr/bech32.rb +203 -0
  41. data/lib/nostr/client.rb +2 -1
  42. data/lib/nostr/crypto.rb +93 -13
  43. data/lib/nostr/errors/error.rb +7 -0
  44. data/lib/nostr/errors/invalid_hrp_error.rb +21 -0
  45. data/lib/nostr/errors/invalid_key_format_error.rb +20 -0
  46. data/lib/nostr/errors/invalid_key_length_error.rb +20 -0
  47. data/lib/nostr/errors/invalid_key_type_error.rb +18 -0
  48. data/lib/nostr/errors/invalid_signature_format_error.rb +18 -0
  49. data/lib/nostr/errors/invalid_signature_length_error.rb +18 -0
  50. data/lib/nostr/errors/invalid_signature_type_error.rb +16 -0
  51. data/lib/nostr/errors/key_validation_error.rb +6 -0
  52. data/lib/nostr/errors/signature_validation_error.rb +6 -0
  53. data/lib/nostr/errors.rb +12 -0
  54. data/lib/nostr/event.rb +40 -13
  55. data/lib/nostr/event_kind.rb +1 -0
  56. data/lib/nostr/events/encrypted_direct_message.rb +8 -7
  57. data/lib/nostr/filter.rb +14 -11
  58. data/lib/nostr/key.rb +100 -0
  59. data/lib/nostr/key_pair.rb +54 -6
  60. data/lib/nostr/keygen.rb +44 -5
  61. data/lib/nostr/private_key.rb +36 -0
  62. data/lib/nostr/public_key.rb +36 -0
  63. data/lib/nostr/relay_message_type.rb +18 -0
  64. data/lib/nostr/signature.rb +67 -0
  65. data/lib/nostr/subscription.rb +2 -2
  66. data/lib/nostr/user.rb +17 -8
  67. data/lib/nostr/version.rb +1 -1
  68. data/lib/nostr.rb +7 -0
  69. data/nostr.gemspec +13 -13
  70. data/sig/nostr/bech32.rbs +14 -0
  71. data/sig/nostr/client.rbs +5 -5
  72. data/sig/nostr/crypto.rbs +8 -5
  73. data/sig/nostr/errors/error.rbs +4 -0
  74. data/sig/nostr/errors/invalid_hrb_error.rbs +6 -0
  75. data/sig/nostr/errors/invalid_key_format_error.rbs +5 -0
  76. data/sig/nostr/errors/invalid_key_length_error.rbs +5 -0
  77. data/sig/nostr/errors/invalid_key_type_error.rbs +5 -0
  78. data/sig/nostr/errors/invalid_signature_format_error.rbs +5 -0
  79. data/sig/nostr/errors/invalid_signature_length_error.rbs +5 -0
  80. data/sig/nostr/errors/invalid_signature_type_error.rbs +5 -0
  81. data/sig/nostr/errors/key_validation_error.rbs +4 -0
  82. data/sig/nostr/errors/signature_validation_error.rbs +4 -0
  83. data/sig/nostr/event.rbs +11 -10
  84. data/sig/nostr/events/encrypted_direct_message.rbs +2 -2
  85. data/sig/nostr/filter.rbs +3 -12
  86. data/sig/nostr/key.rbs +16 -0
  87. data/sig/nostr/key_pair.rbs +8 -3
  88. data/sig/nostr/keygen.rbs +5 -2
  89. data/sig/nostr/private_key.rbs +4 -0
  90. data/sig/nostr/public_key.rbs +4 -0
  91. data/sig/nostr/relay_message_type.rbs +8 -0
  92. data/sig/nostr/signature.rbs +14 -0
  93. data/sig/nostr/user.rbs +4 -8
  94. data/sig/vendor/bech32/nostr/entity.rbs +41 -0
  95. data/sig/vendor/bech32/nostr/nip19.rbs +20 -0
  96. data/sig/vendor/bech32/segwit_addr.rbs +21 -0
  97. data/sig/vendor/bech32.rbs +25 -0
  98. data/sig/vendor/event_emitter.rbs +10 -3
  99. data/sig/vendor/event_machine/channel.rbs +1 -1
  100. data/sig/vendor/faye/websocket/api.rbs +45 -0
  101. data/sig/vendor/faye/websocket/client.rbs +43 -0
  102. data/sig/vendor/faye/websocket.rbs +30 -0
  103. data/sig/vendor/schnorr/signature.rbs +16 -0
  104. data/sig/vendor/schnorr.rbs +3 -1
  105. 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,12 @@
1
+ {
2
+ "scripts": {
3
+ "docs:dev": "vitepress dev",
4
+ "docs:build": "vitepress build",
5
+ "docs:preview": "vitepress preview"
6
+ },
7
+ "devDependencies": {
8
+ "mermaid": "^10.6.1",
9
+ "vitepress": "^1.0.0-rc.25",
10
+ "vitepress-plugin-mermaid": "^2.0.15"
11
+ }
12
+ }
@@ -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
+ ```