nostr 0.4.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
:::
|