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
@@ -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
|
+
```
|
data/docs/core/client.md
ADDED
@@ -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
|
+
:::
|