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.
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
+ :::