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
@@ -0,0 +1,114 @@
1
+ import { defineConfig } from 'vitepress'
2
+ import { withMermaid } from "vitepress-plugin-mermaid";
3
+
4
+ // https://vitepress.dev/reference/site-config
5
+ // https://www.npmjs.com/package/vitepress-plugin-mermaid
6
+ export default defineConfig(withMermaid({
7
+ title: "Nostr",
8
+ description: "Documentation of the Nostr Ruby gem",
9
+ // https://vitepress.dev/reference/site-config#head
10
+ head: [
11
+ ['link', { rel: 'icon', href: '/favicon.ico' }]
12
+ ],
13
+ themeConfig: {
14
+ // https://vitepress.dev/reference/default-theme-last-updated
15
+ lastUpdated: true,
16
+
17
+ // https://vitepress.dev/reference/default-theme-config
18
+ nav: [
19
+ { text: 'Home', link: '/' },
20
+ { text: 'Guide', link: '/getting-started/overview' }
21
+ ],
22
+
23
+ // https://vitepress.dev/reference/default-theme-search
24
+ search: {
25
+ provider: 'local'
26
+ },
27
+
28
+ // https://vitepress.dev/reference/default-theme-sidebar
29
+ sidebar: [
30
+ {
31
+ text: 'Getting started',
32
+ collapsed: false,
33
+ items: [
34
+ { text: 'Overview', link: '/getting-started/overview' },
35
+ { text: 'Installation', link: '/getting-started/installation' },
36
+ ]
37
+ },
38
+ {
39
+ text: 'Core',
40
+ collapsed: false,
41
+ items: [
42
+ { text: 'Client', link: '/core/client' },
43
+ { text: 'Keys', link: '/core/keys' },
44
+ { text: 'User', link: '/core/user' },
45
+ ]
46
+ },
47
+ {
48
+ text: 'Relays',
49
+ items: [
50
+ { text: 'Connecting to a relay', link: '/relays/connecting-to-a-relay' },
51
+ { text: 'Publishing events', link: '/relays/publishing-events' },
52
+ { text: 'Receiving events', link: '/relays/receiving-events' },
53
+ ]
54
+ },
55
+ {
56
+ text: 'Subscriptions',
57
+ collapsed: false,
58
+ items: [
59
+ { text: 'Creating a subscription', link: '/subscriptions/creating-a-subscription' },
60
+ { text: 'Filtering subscription events', link: '/subscriptions/filtering-subscription-events' },
61
+ { text: 'Updating a subscription', link: '/subscriptions/updating-a-subscription' },
62
+ { text: 'Deleting a subscription', link: '/subscriptions/deleting-a-subscription' },
63
+ ]
64
+ },
65
+ {
66
+ text: 'Events',
67
+ link: '/events',
68
+ collapsed: false,
69
+ items: [
70
+ { text: 'Set Metadata', link: '/events/set-metadata' },
71
+ { text: 'Text Note', link: '/events/text-note' },
72
+ { text: 'Recommend Server', link: '/events/recommend-server' },
73
+ { text: 'Contact List', link: '/events/contact-list' },
74
+ { text: 'Encrypted Direct Message', link: '/events/encrypted-direct-message' },
75
+ ]
76
+ },
77
+ {
78
+ text: 'Common use cases',
79
+ collapsed: false,
80
+ items: [
81
+ { text: 'Bech32 enc/decoding (NIP-19)', link: '/common-use-cases/bech32-encoding-and-decoding-(NIP-19)' },
82
+ { text: 'Signing/verifying messages', link: '/common-use-cases/signing-and-verifying-messages' },
83
+ { text: 'Signing/verifying events', link: '/common-use-cases/signing-and-verifying-events' },
84
+ ]
85
+ },
86
+ {
87
+ text: 'Implemented NIPs',
88
+ link: '/implemented-nips',
89
+ },
90
+ ],
91
+
92
+ // https://vitepress.dev/reference/default-theme-config#sociallinks
93
+ socialLinks: [
94
+ { icon: 'github', link: 'https://github.com/wilsonsilva/nostr' }
95
+ ],
96
+
97
+ // https://vitepress.dev/reference/default-theme-edit-link
98
+ editLink: {
99
+ pattern: 'https://github.com/wilsonsilva/nostr/edit/main/docs/:path',
100
+ text: 'Edit this page on GitHub'
101
+ },
102
+
103
+ // https://vitepress.dev/reference/default-theme-footer
104
+ footer: {
105
+ message: 'Released under the <a href="https://github.com/wilsonsilva/nostr/blob/main/LICENSE.txt">MIT License</a>.',
106
+ copyright: 'Copyright © 2023-present <a href="https://github.com/wilsonsilva">Wilson Silva</a>'
107
+ }
108
+ },
109
+
110
+ // https://vitepress.dev/reference/site-config#ignoredeadlinks
111
+ ignoreDeadLinks: [
112
+ /^https?:\/\/localhost/
113
+ ],
114
+ }))
data/docs/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # Nostr Docs
2
+
3
+ VitePress-powered documentation for the Nostr Ruby gem.
4
+
5
+ ## Live Demo
6
+
7
+ https://nostr-ruby.com/
8
+
9
+ ## Development
10
+
11
+ ### Requirements
12
+
13
+ - [Bun](https://bun.sh/)
14
+
15
+ ### Installation
16
+
17
+ ```shell
18
+ bun install
19
+ ```
20
+
21
+ ### Tasks
22
+
23
+ The `docs:dev` script will start a local dev server with instant hot updates. Run it with the following command:
24
+
25
+ ```shell
26
+ bun run docs:dev
27
+ ```
28
+
29
+ Run this command to build the docs:
30
+
31
+ ```shell
32
+ bun run docs:build
33
+ ```
34
+
35
+ Once built, preview it locally by running:
36
+
37
+ ```shell
38
+ bun run docs:preview
39
+ ```
40
+
41
+ The preview command will boot up a local static web server that will serve the output directory .`vitepress/dist` at
42
+ http://localhost:4173. You can use this to make sure everything looks good before pushing to production.
43
+
44
+
@@ -0,0 +1,49 @@
1
+ ---
2
+ outline: deep
3
+ ---
4
+
5
+ # Runtime API Examples
6
+
7
+ This page demonstrates usage of some of the runtime APIs provided by VitePress.
8
+
9
+ The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files:
10
+
11
+ ```md
12
+ <script setup>
13
+ import { useData } from 'vitepress'
14
+
15
+ const { theme, page, frontmatter } = useData()
16
+ </script>
17
+
18
+ ## Results
19
+
20
+ ### Theme Data
21
+ <pre>{{ theme }}</pre>
22
+
23
+ ### Page Data
24
+ <pre>{{ page }}</pre>
25
+
26
+ ### Page Frontmatter
27
+ <pre>{{ frontmatter }}</pre>
28
+ ```
29
+
30
+ <script setup>
31
+ import { useData } from 'vitepress'
32
+
33
+ const { site, theme, page, frontmatter } = useData()
34
+ </script>
35
+
36
+ ## Results
37
+
38
+ ### Theme Data
39
+ <pre>{{ theme }}</pre>
40
+
41
+ ### Page Data
42
+ <pre>{{ page }}</pre>
43
+
44
+ ### Page Frontmatter
45
+ <pre>{{ frontmatter }}</pre>
46
+
47
+ ## More
48
+
49
+ Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata).
data/docs/bun.lockb ADDED
Binary file
@@ -0,0 +1,190 @@
1
+ # Encoding/decoding bech-32 strings (NIP-19)
2
+
3
+ [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md) standardizes bech32-formatted strings that can be
4
+ used to display keys, ids and other information in clients. These formats are not meant to be used anywhere in the core
5
+ protocol, they are only meant for displaying to users, copy-pasting, sharing, rendering QR codes and inputting data.
6
+
7
+
8
+ In order to guarantee the deterministic nature of the documentation, the examples below assume that there is a `keypair`
9
+ variable with the following values:
10
+
11
+ ```ruby
12
+ keypair = Nostr::KeyPair.new(
13
+ private_key: Nostr::PrivateKey.new('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'),
14
+ public_key: Nostr::PublicKey.new('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'),
15
+ )
16
+
17
+ keypair.private_key # => '67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'
18
+ keypair.public_key # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
19
+ ```
20
+
21
+ ## Public key (npub)
22
+
23
+ ### Encoding
24
+
25
+ ```ruby
26
+ npub = Nostr::Bech32.npub_encode('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e')
27
+ npub # => 'npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'
28
+ ```
29
+
30
+ ### Decoding
31
+
32
+ ```ruby
33
+ type, public_key = Nostr::Bech32.decode('npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg')
34
+ type # => 'npub'
35
+ public_key # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
36
+ ```
37
+
38
+ ## Private key (nsec)
39
+
40
+ ### Encoding
41
+
42
+ ```ruby
43
+ nsec = Nostr::Bech32.nsec_encode('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa')
44
+ nsec # => 'nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5'
45
+ ```
46
+
47
+ ### Decoding
48
+
49
+ ```ruby
50
+ type, private_key = Nostr::Bech32.decode('nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5')
51
+ type # => 'npub'
52
+ private_key # => '67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'
53
+ ```
54
+
55
+ ## Relay (nrelay)
56
+
57
+ ### Encoding
58
+
59
+ ```ruby
60
+ nrelay = Nostr::Bech32.nrelay_encode('wss://relay.damus.io')
61
+ nrelay # => 'nrelay1qq28wumn8ghj7un9d3shjtnyv9kh2uewd9hsc5zt2x'
62
+ ```
63
+
64
+ ### Decoding
65
+
66
+ ```ruby
67
+ type, data = Nostr::Bech32.decode('nrelay1qq28wumn8ghj7un9d3shjtnyv9kh2uewd9hsc5zt2x')
68
+
69
+ type # => 'nrelay'
70
+ data.entries.first.label # => 'relay'
71
+ data.entries.first.value # => 'wss://relay.damus.io'
72
+ ```
73
+
74
+ ## Event (nevent)
75
+
76
+ ### Encoding
77
+
78
+ ```ruby{8-12}
79
+ user = Nostr::User.new(keypair: keypair)
80
+ text_note_event = user.create_event(
81
+ kind: Nostr::EventKind::TEXT_NOTE,
82
+ created_at: 1700467997,
83
+ content: 'Your feedback is appreciated, now pay $8'
84
+ )
85
+
86
+ nevent = Nostr::Bech32.nevent_encode(
87
+ id: text_note_event.id,
88
+ relays: ['wss://relay.damus.io', 'wss://nos.lol'],
89
+ kind: Nostr::EventKind::TEXT_NOTE,
90
+ )
91
+
92
+ nevent # => 'nevent1qgsqlkuslr3rf56qpmd0m5ndfyl39m7q6l0zcmuly8ue0praxwkjagcpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dspsgqqqqqqs03k8v3'
93
+ ```
94
+
95
+ ### Decoding
96
+
97
+ ```ruby
98
+ type, event = Nostr::Bech32.decode('nevent1qgsqlkuslr3rf56qpmd0m5ndfyl39m7q6l0zcmuly8ue0praxwkjagcpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dspsgqqqqqqs03k8v3')
99
+
100
+ type # => 'nevent'
101
+ event.entries[0].label # => 'author'
102
+ event.entries[0].value # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
103
+ event.entries[1].relay # => 'relay'
104
+ event.entries[1].value # => 'wss://relay.damus.io'
105
+ event.entries[2].label # => 'relay'
106
+ event.entries[2].value # => 'wss://nos.lol'
107
+ event.entries[3].label # => 'kind'
108
+ event.entries[3].value # => 1
109
+ ```
110
+
111
+ ## Address (naddr)
112
+
113
+ ### Encoding
114
+
115
+ ```ruby
116
+ naddr = Nostr::Bech32.naddr_encode(
117
+ pubkey: keypair.public_key,
118
+ relays: ['wss://relay.damus.io', 'wss://nos.lol'],
119
+ kind: Nostr::EventKind::TEXT_NOTE,
120
+ identifier: 'damus',
121
+ )
122
+
123
+ naddr # => 'naddr1qgs8ul5ug253hlh3n75jne0a5xmjur4urfxpzst88cnegg6ds6ka7nspz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dspsgqqqqqqsqptyv9kh2uc3qfs2p'
124
+ ```
125
+
126
+ ### Decoding
127
+
128
+ ```ruby
129
+ type, addr = Nostr::Bech32.decode('naddr1qgs8ul5ug253hlh3n75jne0a5xmjur4urfxpzst88cnegg6ds6ka7nspz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dspsgqqqqqqsqptyv9kh2uc3qfs2p')
130
+
131
+ type # => 'naddr'
132
+ addr.entries[0].label # => 'author'
133
+ addr.entries[0].value # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
134
+ addr.entries[1].label # => 'relay'
135
+ addr.entries[1].value # => 'wss://relay.damus.io'
136
+ addr.entries[2].label # => 'relay'
137
+ addr.entries[2].value # => 'wss://nos.lol'
138
+ addr.entries[3].label # => 'kind'
139
+ addr.entries[3].value # => 1
140
+ addr.entries[4].label # => 'identifier'
141
+ addr.entries[4].value # => 'damus'
142
+ ```
143
+
144
+ ## Profile (nprofile)
145
+
146
+ ### Encoding
147
+ ```ruby
148
+ relay_urls = %w[wss://relay.damus.io wss://nos.lol]
149
+ nprofile = Nostr::Bech32.nprofile_encode(pubkey: keypair.public_key, relays: relay_urls)
150
+
151
+ nprofile # => nprofile1qqs8ul5ug253hlh3n75jne0a5xmjur4urfxpzst88cnegg6ds6ka7nspz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dsxe58m5
152
+ ```
153
+
154
+ ### Decoding
155
+
156
+ ```ruby
157
+ type, profile = Nostr::Bech32.decode('nprofile1qqs8ul5ug253hlh3n75jne0a5xmjur4urfxpzst88cnegg6ds6ka7nspz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dsxe58m5')
158
+
159
+ type # => 'nprofile'
160
+ profile.entries[0].label # => 'pubkey'
161
+ profile.entries[0].value # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
162
+ profile.entries[1].label # => 'relay'
163
+ profile.entries[1].value # => 'wss://relay.damus.io'
164
+ profile.entries[2].label # => 'relay'
165
+ profile.entries[2].value # => 'wss://nos.lol'
166
+ ```
167
+
168
+ ## Other simple types (note)
169
+
170
+ ### Encoding
171
+
172
+ ```ruby{8-9}
173
+ user = Nostr::User.new(keypair: keypair)
174
+ text_note_event = user.create_event(
175
+ kind: Nostr::EventKind::TEXT_NOTE,
176
+ created_at: 1700467997,
177
+ content: 'Your feedback is appreciated, now pay $8'
178
+ )
179
+
180
+ note = Nostr::Bech32.encode(hrp: 'note', data: text_note_event.id)
181
+ note # => 'note10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qnx3ujq'
182
+ ```
183
+
184
+ ### Decoding
185
+
186
+ ```ruby
187
+ type, note = Nostr::Bech32.decode('note1pldep78zxnf5qrk6lhfx6jflzthup47793he7g0ej7z86vad963s42v0rr')
188
+ type # => 'note'
189
+ note # => '0fdb90f8e234d3400edafdd26d493f12efc0d7de2c6f9f21f997847d33ad2ea3'
190
+ ```
@@ -0,0 +1,50 @@
1
+ # Signing and verifying events
2
+
3
+ Signing an event in Nostr proves it was sent by the owner of a specific private key.
4
+
5
+ ## Signing an event
6
+
7
+ To sign an event, use the private key associated with the event's creator. Here's how to sign a message using a
8
+ predefined keypair:
9
+
10
+ ```ruby{14}
11
+ require 'nostr'
12
+
13
+ private_key = Nostr::PrivateKey.new('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'),
14
+ public_key = Nostr::PublicKey.new('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'),
15
+
16
+ event = Nostr::Event.new(
17
+ pubkey: public_key.to_s,
18
+ kind: Nostr::EventKind::TEXT_NOTE,
19
+ content: 'We did it with security, now we’re going to do it with the economy.',
20
+ created_at: Time.now.to_i,
21
+ )
22
+
23
+ # Sign the event with the private key
24
+ event.sign(private_key)
25
+
26
+ puts "Event ID: #{event.id}"
27
+ puts "Event Signature: #{event.sig}"
28
+ ```
29
+
30
+ ## Verifying an event's signature
31
+
32
+ To verify an event, you must ensure the event's signature is valid. This indicates the event was created by the owner
33
+ of the corresponding public key.
34
+
35
+ When the event was signed with the private key corresponding to the public key, the `verify_signature` method will
36
+ return `true`.
37
+
38
+ ```ruby
39
+ event.verify_signature # => true
40
+ ```
41
+
42
+ And when the event was not signed with the private key corresponding to the public key, the `verify_signature` method
43
+ will return `false`.
44
+
45
+ An event without an `id`, `pubkey`, `sig` is considered invalid and will return `false` when calling `verify_signature`.
46
+
47
+ ```ruby
48
+ other_public_key = Nostr::PublicKey.new('10be96d345ed58d923a734560680f1adfd2b1006c28ac93b8e1b032a9a32c6e9')
49
+ event.verify_signature # => false
50
+ ```
@@ -0,0 +1,43 @@
1
+ # Signing and verifying messages
2
+
3
+ Signing a message in Nostr proves it was sent by the owner of a specific private key.
4
+
5
+ ## Signing a message
6
+
7
+ To sign a message, you'll need a private key. Here's how to sign a message using a predefined keypair:
8
+
9
+ ```ruby{9}
10
+ require 'nostr'
11
+
12
+ private_key = Nostr::PrivateKey.new('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'),
13
+ public_key = Nostr::PublicKey.new('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'),
14
+
15
+ message = 'We did it with security, now we’re going to do it with the economy.' # The message you want to sign
16
+
17
+ crypto = Nostr::Crypto.new
18
+ signature = crypto.sign_message(message, private_key)
19
+ signature # => "d7a0aac1fadcddf1aa2949bedfcdf25ce0c1604e648e55d31431fdacbff8e8256f7c2166d98292f80bc5f79105a0b6e8a89236a47d97cf5d0e7cc1ebf34dea5c"
20
+ ```
21
+
22
+ ## Verifying a signature
23
+
24
+ To verify a signature, you need the original message, the public key of the signer, and the signature.
25
+
26
+ ```ruby
27
+ crypto.valid_sig?(message, public_key, signature) # => true
28
+ crypto.check_sig!(message, public_key, signature) # => true
29
+ ```
30
+
31
+ When the message was not signed with the private key corresponding to the public key, the `valid_sig?` method will return `false`.
32
+
33
+ ```ruby
34
+ other_public_key = Nostr::PublicKey.new('10be96d345ed58d923a734560680f1adfd2b1006c28ac93b8e1b032a9a32c6e9')
35
+ crypto.valid_sig?(message, public_key, signature) # => false
36
+ ```
37
+
38
+ And when the message was not signed with the private key corresponding to the public key, the `check_sig!` method will raise an error.
39
+
40
+ ```ruby
41
+ other_public_key = Nostr::PublicKey.new('10be96d345ed58d923a734560680f1adfd2b1006c28ac93b8e1b032a9a32c6e9')
42
+ crypto.check_sig!(message, other_public_key, signature) # => Schnorr::InvalidSignatureError: signature verification failed
43
+ ```
@@ -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
+ :::