nostr-tools 0.24.1 → 1.0.0-alpha2
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.
- package/.eslintrc.json +32 -19
- package/.github/workflows/npm-publish.yml +19 -0
- package/.github/workflows/test.yml +17 -0
- package/README.md +130 -59
- package/build.js +47 -0
- package/event.test.js +49 -0
- package/{event.js → event.ts} +23 -12
- package/filter.test.js +42 -0
- package/{filter.js → filter.ts} +23 -4
- package/index.ts +8 -0
- package/keys.test.js +20 -0
- package/{keys.js → keys.ts} +2 -2
- package/lib/nostr.bundle.js +18864 -0
- package/lib/nostr.bundle.js.map +7 -0
- package/lib/nostr.cjs.js +483 -0
- package/lib/nostr.cjs.js.map +7 -0
- package/lib/nostr.esm.js +464 -0
- package/lib/nostr.esm.js.map +7 -0
- package/lib/nostr.js.map +7 -0
- package/nip04.test.js +14 -0
- package/{nip04.js → nip04.ts} +10 -7
- package/nip05.ts +31 -0
- package/nip06.ts +26 -0
- package/nip19.ts +29 -0
- package/package.json +19 -21
- package/relay.test.js +117 -0
- package/relay.ts +311 -0
- package/tsconfig.json +13 -23
- package/build.cjs +0 -25
- package/index.d.ts +0 -107
- package/index.js +0 -27
- package/index.test-d.ts +0 -42
- package/nip05.js +0 -28
- package/nip06.js +0 -26
- package/pool.js +0 -206
- package/relay.js +0 -195
package/.eslintrc.json
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"root": true,
|
|
3
|
+
|
|
4
|
+
"parser": "@typescript-eslint/parser",
|
|
5
|
+
"plugins": ["@typescript-eslint"],
|
|
6
|
+
|
|
3
7
|
"parserOptions": {
|
|
4
8
|
"ecmaVersion": 9,
|
|
5
9
|
"ecmaFeatures": {
|
|
@@ -14,9 +18,7 @@
|
|
|
14
18
|
"node": true
|
|
15
19
|
},
|
|
16
20
|
|
|
17
|
-
"plugins": [
|
|
18
|
-
"babel"
|
|
19
|
-
],
|
|
21
|
+
"plugins": ["babel"],
|
|
20
22
|
|
|
21
23
|
"globals": {
|
|
22
24
|
"document": false,
|
|
@@ -33,23 +35,23 @@
|
|
|
33
35
|
|
|
34
36
|
"rules": {
|
|
35
37
|
"accessor-pairs": 2,
|
|
36
|
-
"arrow-spacing": [2, {
|
|
38
|
+
"arrow-spacing": [2, {"before": true, "after": true}],
|
|
37
39
|
"block-spacing": [2, "always"],
|
|
38
|
-
"brace-style": [2, "1tbs", {
|
|
40
|
+
"brace-style": [2, "1tbs", {"allowSingleLine": true}],
|
|
39
41
|
"comma-dangle": 0,
|
|
40
|
-
"comma-spacing": [2, {
|
|
42
|
+
"comma-spacing": [2, {"before": false, "after": true}],
|
|
41
43
|
"comma-style": [2, "last"],
|
|
42
44
|
"constructor-super": 2,
|
|
43
45
|
"curly": [0, "multi-line"],
|
|
44
46
|
"dot-location": [2, "property"],
|
|
45
47
|
"eol-last": 2,
|
|
46
48
|
"eqeqeq": [2, "allow-null"],
|
|
47
|
-
"generator-star-spacing": [2, {
|
|
48
|
-
"handle-callback-err": [2, "^(err|error)$"
|
|
49
|
+
"generator-star-spacing": [2, {"before": true, "after": true}],
|
|
50
|
+
"handle-callback-err": [2, "^(err|error)$"],
|
|
49
51
|
"indent": 0,
|
|
50
52
|
"jsx-quotes": [2, "prefer-double"],
|
|
51
|
-
"key-spacing": [2, {
|
|
52
|
-
"keyword-spacing": [2, {
|
|
53
|
+
"key-spacing": [2, {"beforeColon": false, "afterColon": true}],
|
|
54
|
+
"keyword-spacing": [2, {"before": true, "after": true}],
|
|
53
55
|
"new-cap": 0,
|
|
54
56
|
"new-parens": 0,
|
|
55
57
|
"no-array-constructor": 2,
|
|
@@ -81,12 +83,12 @@
|
|
|
81
83
|
"no-irregular-whitespace": 2,
|
|
82
84
|
"no-iterator": 2,
|
|
83
85
|
"no-label-var": 2,
|
|
84
|
-
"no-labels": [2, {
|
|
86
|
+
"no-labels": [2, {"allowLoop": false, "allowSwitch": false}],
|
|
85
87
|
"no-lone-blocks": 2,
|
|
86
88
|
"no-mixed-spaces-and-tabs": 2,
|
|
87
89
|
"no-multi-spaces": 2,
|
|
88
90
|
"no-multi-str": 2,
|
|
89
|
-
"no-multiple-empty-lines": [2, {
|
|
91
|
+
"no-multiple-empty-lines": [2, {"max": 2}],
|
|
90
92
|
"no-native-reassign": 2,
|
|
91
93
|
"no-negated-in-lhs": 2,
|
|
92
94
|
"no-new": 0,
|
|
@@ -115,23 +117,34 @@
|
|
|
115
117
|
"no-undef": 2,
|
|
116
118
|
"no-undef-init": 2,
|
|
117
119
|
"no-unexpected-multiline": 2,
|
|
118
|
-
"no-unneeded-ternary": [2, {
|
|
120
|
+
"no-unneeded-ternary": [2, {"defaultAssignment": false}],
|
|
119
121
|
"no-unreachable": 2,
|
|
120
|
-
"no-unused-vars": [
|
|
122
|
+
"no-unused-vars": [
|
|
123
|
+
2,
|
|
124
|
+
{"vars": "local", "args": "none", "varsIgnorePattern": "^_"}
|
|
125
|
+
],
|
|
121
126
|
"no-useless-call": 2,
|
|
122
127
|
"no-useless-constructor": 2,
|
|
123
128
|
"no-with": 2,
|
|
124
|
-
"one-var": [0, {
|
|
125
|
-
"operator-linebreak": [
|
|
129
|
+
"one-var": [0, {"initialized": "never"}],
|
|
130
|
+
"operator-linebreak": [
|
|
131
|
+
2,
|
|
132
|
+
"after",
|
|
133
|
+
{"overrides": {"?": "before", ":": "before"}}
|
|
134
|
+
],
|
|
126
135
|
"padded-blocks": [2, "never"],
|
|
127
|
-
"quotes": [
|
|
136
|
+
"quotes": [
|
|
137
|
+
2,
|
|
138
|
+
"single",
|
|
139
|
+
{"avoidEscape": true, "allowTemplateLiterals": true}
|
|
140
|
+
],
|
|
128
141
|
"semi": [2, "never"],
|
|
129
|
-
"semi-spacing": [2, {
|
|
142
|
+
"semi-spacing": [2, {"before": false, "after": true}],
|
|
130
143
|
"space-before-blocks": [2, "always"],
|
|
131
144
|
"space-before-function-paren": 0,
|
|
132
145
|
"space-in-parens": [2, "never"],
|
|
133
146
|
"space-infix-ops": 2,
|
|
134
|
-
"space-unary-ops": [2, {
|
|
147
|
+
"space-unary-ops": [2, {"words": true, "nonwords": false}],
|
|
135
148
|
"spaced-comment": 0,
|
|
136
149
|
"template-curly-spacing": [2, "never"],
|
|
137
150
|
"use-isnan": 2,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: publish npm package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: [v*]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish-npm:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v3
|
|
12
|
+
- uses: actions/setup-node@v3
|
|
13
|
+
with:
|
|
14
|
+
node-version: 18
|
|
15
|
+
- run: yarn --ignore-engines
|
|
16
|
+
- run: node build.js
|
|
17
|
+
- run: npm publish
|
|
18
|
+
env:
|
|
19
|
+
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: test every commit
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- master
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v2
|
|
12
|
+
- uses: actions/setup-node@v3
|
|
13
|
+
with:
|
|
14
|
+
node-version: 18
|
|
15
|
+
- run: yarn --ignore-engines
|
|
16
|
+
- run: node build.js
|
|
17
|
+
- run: yarn test
|
package/README.md
CHANGED
|
@@ -4,89 +4,160 @@ Tools for developing [Nostr](https://github.com/fiatjaf/nostr) clients.
|
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
import {relayPool} from 'nostr-tools'
|
|
7
|
+
### Generating a private key and a public key
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
```js
|
|
10
|
+
import { generatePrivateKey, getPublicKey } from 'nostr-tools'
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
let sk = generatePrivateKey() # `sk` is a hex string
|
|
13
|
+
let pk = getPublicKey(sk) # `pk` is a hex string
|
|
14
|
+
```
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
pool.addRelay('ws://other.relay.cool', {read: true, write: true})
|
|
16
|
+
### Creating, signing and verifying events
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
```js
|
|
19
|
+
import {
|
|
20
|
+
validateEvent,
|
|
21
|
+
verifySignature,
|
|
22
|
+
signEvent,
|
|
23
|
+
getEventHash,
|
|
24
|
+
getPublicKey
|
|
25
|
+
} from 'nostr-tools'
|
|
26
|
+
|
|
27
|
+
let event = {
|
|
28
|
+
kind: 1,
|
|
29
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
30
|
+
tags: [],
|
|
31
|
+
content: 'hello'
|
|
20
32
|
}
|
|
21
33
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// or bulk follow
|
|
27
|
-
pool.sub({cb:(event, relay) => {...}, filter: {authors: ['<hex1>', '<hex2>', ..., '<hexn>']}})
|
|
28
|
-
|
|
29
|
-
// reuse a subscription channel
|
|
30
|
-
const mySubscription = pool.sub({cb: ..., filter: ....})
|
|
31
|
-
mySubscription.sub({filter: ....})
|
|
32
|
-
mySubscription.sub({cb: ...})
|
|
33
|
-
mySubscription.unsub()
|
|
34
|
-
|
|
35
|
-
// get specific event
|
|
36
|
-
const specificChannel = pool.sub({
|
|
37
|
-
cb: (event, relay) => {
|
|
38
|
-
console.log('got specific event from relay', event, relay)
|
|
39
|
-
specificChannel.unsub()
|
|
40
|
-
},
|
|
41
|
-
filter: {id: '<hex>'}
|
|
42
|
-
})
|
|
34
|
+
event.id = getEventHash(event.id)
|
|
35
|
+
event.pubkey = getPublicKey(privateKey)
|
|
36
|
+
event.sig = await signEvent(event, privateKey)
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
let ok = validateEvent(event)
|
|
39
|
+
let veryOk = await verifySignature(event)
|
|
40
|
+
```
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
pool.sub({cb: (event, relay) => {...}, filter: {}})
|
|
42
|
+
### Interacting with a relay
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
```js
|
|
45
|
+
import {
|
|
46
|
+
relayInit,
|
|
47
|
+
generatePrivateKey,
|
|
48
|
+
getPublicKey,
|
|
49
|
+
getEventHash,
|
|
50
|
+
signEvent
|
|
51
|
+
} from 'nostr-tools'
|
|
52
|
+
|
|
53
|
+
const relay = relayInit('wss://relay.example.com')
|
|
54
|
+
relay.connect()
|
|
55
|
+
|
|
56
|
+
relay.on('connect', () => {
|
|
57
|
+
console.log(`connected to ${relay.url}`)
|
|
58
|
+
})
|
|
59
|
+
relay.on('error', () => {
|
|
60
|
+
console.log(`failed to connect to ${relay.url}`)
|
|
61
|
+
})
|
|
52
62
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
// let's query for an event that exists
|
|
64
|
+
let sub = relay.sub([
|
|
65
|
+
{
|
|
66
|
+
ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027']
|
|
57
67
|
}
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
])
|
|
69
|
+
sub.on('event', event => {
|
|
70
|
+
console.log('we got the event we wanted:', event)
|
|
71
|
+
})
|
|
72
|
+
sub.on('eose', () => {
|
|
73
|
+
sub.unsub()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// let's publish a new event while simultaneously monitoring the relay for it
|
|
77
|
+
let sk = generatePrivateKey()
|
|
78
|
+
let pk = getPublicKey(sk)
|
|
79
|
+
|
|
80
|
+
let sub = relay.sub([
|
|
81
|
+
{
|
|
82
|
+
kinds: [1],
|
|
83
|
+
authors: [pk]
|
|
60
84
|
}
|
|
85
|
+
])
|
|
86
|
+
|
|
87
|
+
sub.on('event', event => {
|
|
88
|
+
console.log('got event:', event)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
let event = {
|
|
92
|
+
kind: 1,
|
|
93
|
+
pubkey: pk,
|
|
94
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
95
|
+
tags: [],
|
|
96
|
+
content: 'hello world'
|
|
97
|
+
}
|
|
98
|
+
event.id = getEventHash(event)
|
|
99
|
+
event.sig = await signEvent(event, sk)
|
|
100
|
+
|
|
101
|
+
let pub = relay.publish(event)
|
|
102
|
+
pub.on('ok', () => {
|
|
103
|
+
console.log(`{relay.url} has accepted our event`)
|
|
104
|
+
})
|
|
105
|
+
pub.on('seen', () => {
|
|
106
|
+
console.log(`we saw the event on {relay.url}`)
|
|
107
|
+
})
|
|
108
|
+
pub.on('failed', reason => {
|
|
109
|
+
console.log(`failed to publish to {relay.url}: ${reason}`)
|
|
61
110
|
})
|
|
62
|
-
// it will be signed automatically with the key supplied above
|
|
63
|
-
// or pass an already signed event to bypass this
|
|
64
111
|
|
|
65
|
-
|
|
66
|
-
pool.addRelay('<url>')
|
|
67
|
-
// will automatically subscribe to the all the events called with .sub above
|
|
112
|
+
await relay.close()
|
|
68
113
|
```
|
|
69
114
|
|
|
70
|
-
|
|
115
|
+
### Encrypting and decrypting direct messages
|
|
71
116
|
|
|
72
|
-
|
|
117
|
+
```js
|
|
118
|
+
import {nip04, getPublicKey, generatePrivateKey} from 'nostr-tools'
|
|
119
|
+
|
|
120
|
+
// sender
|
|
121
|
+
let sk1 = generatePrivateKey()
|
|
122
|
+
let pk1 = getPublicKey(sk1)
|
|
123
|
+
|
|
124
|
+
// receiver
|
|
125
|
+
let sk2 = generatePrivateKey()
|
|
126
|
+
let pk2 = getPublicKey(sk2)
|
|
127
|
+
|
|
128
|
+
// on the sender side
|
|
129
|
+
let message = 'hello'
|
|
130
|
+
let ciphertext = nip04.encrypt(sk1, pk2, 'hello')
|
|
131
|
+
|
|
132
|
+
let event = {
|
|
133
|
+
kind: 4,
|
|
134
|
+
pubkey: pk1,
|
|
135
|
+
tags: [['p', pk2]],
|
|
136
|
+
content: ciphertext,
|
|
137
|
+
...otherProperties
|
|
138
|
+
}
|
|
73
139
|
|
|
74
|
-
|
|
140
|
+
sendEvent(event)
|
|
75
141
|
|
|
76
|
-
|
|
142
|
+
// on the receiver side
|
|
77
143
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
144
|
+
sub.on('event', (event) => {
|
|
145
|
+
let sender = event.tags.find(([k, v]) => k === 'p' && && v && v !== '')[1]
|
|
146
|
+
pk1 === sender
|
|
147
|
+
let plaintext = nip04.decrypt(sk2, pk1, event.content)
|
|
148
|
+
})
|
|
83
149
|
```
|
|
84
150
|
|
|
85
|
-
|
|
151
|
+
Please consult the tests or [the source code](https://github.com/fiatjaf/nostr-tools) for more information that isn't available here.
|
|
86
152
|
|
|
87
|
-
|
|
153
|
+
### Using from the browser (if you don't want to use a bundler)
|
|
88
154
|
|
|
89
|
-
|
|
155
|
+
```html
|
|
156
|
+
<script src="https://unpkg.com/nostr-tools/nostr.bundle.js"></script>
|
|
157
|
+
<script>
|
|
158
|
+
window.NostrTools.generatePrivateKey('...') // and so on
|
|
159
|
+
</script>
|
|
160
|
+
```
|
|
90
161
|
|
|
91
162
|
## License
|
|
92
163
|
|
package/build.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const esbuild = require('esbuild')
|
|
4
|
+
const alias = require('esbuild-plugin-alias')
|
|
5
|
+
|
|
6
|
+
let common = {
|
|
7
|
+
entryPoints: ['index.ts'],
|
|
8
|
+
bundle: true,
|
|
9
|
+
plugins: [
|
|
10
|
+
alias({
|
|
11
|
+
stream: require.resolve('readable-stream')
|
|
12
|
+
})
|
|
13
|
+
],
|
|
14
|
+
sourcemap: 'external'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
esbuild
|
|
18
|
+
.build({
|
|
19
|
+
...common,
|
|
20
|
+
outfile: 'lib/nostr.esm.js',
|
|
21
|
+
format: 'esm',
|
|
22
|
+
packages: 'external'
|
|
23
|
+
})
|
|
24
|
+
.then(() => console.log('esm build success.'))
|
|
25
|
+
|
|
26
|
+
esbuild
|
|
27
|
+
.build({
|
|
28
|
+
...common,
|
|
29
|
+
outfile: 'lib/nostr.cjs.js',
|
|
30
|
+
format: 'cjs',
|
|
31
|
+
packages: 'external'
|
|
32
|
+
})
|
|
33
|
+
.then(() => console.log('cjs build success.'))
|
|
34
|
+
|
|
35
|
+
esbuild
|
|
36
|
+
.build({
|
|
37
|
+
...common,
|
|
38
|
+
outfile: 'lib/nostr.bundle.js',
|
|
39
|
+
format: 'iife',
|
|
40
|
+
globalName: 'NostrTools',
|
|
41
|
+
define: {
|
|
42
|
+
window: 'self',
|
|
43
|
+
global: 'self',
|
|
44
|
+
process: '{"env": {}}'
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
.then(() => console.log('standalone build success.'))
|
package/event.test.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/* eslint-env jest */
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
validateEvent,
|
|
5
|
+
verifySignature,
|
|
6
|
+
signEvent,
|
|
7
|
+
getEventHash,
|
|
8
|
+
getPublicKey
|
|
9
|
+
} = require('./lib/nostr.cjs')
|
|
10
|
+
|
|
11
|
+
const event = {
|
|
12
|
+
id: 'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027',
|
|
13
|
+
kind: 1,
|
|
14
|
+
pubkey: '22a12a128a3be27cd7fb250cbe796e692896398dc1440ae3fa567812c8107c1c',
|
|
15
|
+
created_at: 1670869179,
|
|
16
|
+
content:
|
|
17
|
+
'NOSTR "WINE-ACCOUNT" WITH HARVEST DATE STAMPED\n\n\n"The older the wine, the greater its reputation"\n\n\n22a12a128a3be27cd7fb250cbe796e692896398dc1440ae3fa567812c8107c1c\n\n\nNWA 2022-12-12\nAA',
|
|
18
|
+
tags: [['client', 'astral']],
|
|
19
|
+
sig: 'f110e4fdf67835fb07abc72469933c40bdc7334615610cade9554bf00945a1cebf84f8d079ec325d26fefd76fe51cb589bdbe208ac9cdbd63351ddad24a57559'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const unsigned = {
|
|
23
|
+
created_at: 1671217411,
|
|
24
|
+
kind: 0,
|
|
25
|
+
tags: [],
|
|
26
|
+
content:
|
|
27
|
+
'{"name":"fiatjaf","about":"buy my merch at fiatjaf store","picture":"https://fiatjaf.com/static/favicon.jpg","nip05":"_@fiatjaf.com"}'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const privateKey =
|
|
31
|
+
'5c6c25b7ef18d8633e97512159954e1aa22809c6b763e94b9f91071836d00217'
|
|
32
|
+
|
|
33
|
+
test('validate event', () => {
|
|
34
|
+
expect(validateEvent(event)).toBeTruthy()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('check signature', async () => {
|
|
38
|
+
expect(await verifySignature(event)).toBeTruthy()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('sign event', async () => {
|
|
42
|
+
let sig = await signEvent(unsigned, privateKey)
|
|
43
|
+
let hash = getEventHash(unsigned)
|
|
44
|
+
let pubkey = getPublicKey(privateKey)
|
|
45
|
+
|
|
46
|
+
let signed = {...unsigned, id: hash, sig, pubkey}
|
|
47
|
+
|
|
48
|
+
expect(await verifySignature(signed)).toBeTruthy()
|
|
49
|
+
})
|
package/{event.js → event.ts}
RENAMED
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
import {Buffer} from 'buffer'
|
|
2
|
-
|
|
2
|
+
// @ts-ignore
|
|
3
3
|
import * as secp256k1 from '@noble/secp256k1'
|
|
4
|
+
import {sha256} from '@noble/hashes/sha256'
|
|
4
5
|
|
|
5
|
-
export
|
|
6
|
+
export type Event = {
|
|
7
|
+
id?: string
|
|
8
|
+
sig?: string
|
|
9
|
+
kind: number
|
|
10
|
+
tags: string[][]
|
|
11
|
+
pubkey: string
|
|
12
|
+
content: string
|
|
13
|
+
created_at: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getBlankEvent(): Event {
|
|
6
17
|
return {
|
|
7
18
|
kind: 255,
|
|
8
|
-
pubkey:
|
|
19
|
+
pubkey: '',
|
|
9
20
|
content: '',
|
|
10
21
|
tags: [],
|
|
11
22
|
created_at: 0
|
|
12
23
|
}
|
|
13
24
|
}
|
|
14
25
|
|
|
15
|
-
export function serializeEvent(evt) {
|
|
26
|
+
export function serializeEvent(evt: Event): string {
|
|
16
27
|
return JSON.stringify([
|
|
17
28
|
0,
|
|
18
29
|
evt.pubkey,
|
|
@@ -23,14 +34,12 @@ export function serializeEvent(evt) {
|
|
|
23
34
|
])
|
|
24
35
|
}
|
|
25
36
|
|
|
26
|
-
export function getEventHash(event) {
|
|
27
|
-
let eventHash =
|
|
28
|
-
.update(Buffer.from(serializeEvent(event)))
|
|
29
|
-
.digest()
|
|
37
|
+
export function getEventHash(event: Event): string {
|
|
38
|
+
let eventHash = sha256(Buffer.from(serializeEvent(event)))
|
|
30
39
|
return Buffer.from(eventHash).toString('hex')
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
export function validateEvent(event) {
|
|
42
|
+
export function validateEvent(event: Event): boolean {
|
|
34
43
|
if (event.id !== getEventHash(event)) return false
|
|
35
44
|
if (typeof event.content !== 'string') return false
|
|
36
45
|
if (typeof event.created_at !== 'number') return false
|
|
@@ -47,12 +56,14 @@ export function validateEvent(event) {
|
|
|
47
56
|
return true
|
|
48
57
|
}
|
|
49
58
|
|
|
50
|
-
export function verifySignature(
|
|
59
|
+
export function verifySignature(
|
|
60
|
+
event: Event & {id: string; sig: string}
|
|
61
|
+
): Promise<boolean> {
|
|
51
62
|
return secp256k1.schnorr.verify(event.sig, event.id, event.pubkey)
|
|
52
63
|
}
|
|
53
64
|
|
|
54
|
-
export async function signEvent(event, key) {
|
|
65
|
+
export async function signEvent(event: Event, key: string): Promise<string> {
|
|
55
66
|
return Buffer.from(
|
|
56
|
-
await secp256k1.schnorr.sign(getEventHash(event), key)
|
|
67
|
+
await secp256k1.schnorr.sign(event.id || getEventHash(event), key)
|
|
57
68
|
).toString('hex')
|
|
58
69
|
}
|
package/filter.test.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* eslint-env jest */
|
|
2
|
+
|
|
3
|
+
const {matchFilters} = require('./lib/nostr.cjs')
|
|
4
|
+
|
|
5
|
+
test('test if filters match', () => {
|
|
6
|
+
;[
|
|
7
|
+
{
|
|
8
|
+
filters: [{ids: ['i']}],
|
|
9
|
+
good: [{id: 'i'}],
|
|
10
|
+
bad: [{id: 'j'}]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
filters: [{authors: ['abc']}, {kinds: [1, 3]}],
|
|
14
|
+
good: [
|
|
15
|
+
{pubkey: 'xyz', kind: 3},
|
|
16
|
+
{pubkey: 'abc', kind: 12},
|
|
17
|
+
{pubkey: 'abc', kind: 1}
|
|
18
|
+
],
|
|
19
|
+
bad: [{pubkey: 'hhh', kind: 12}]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
filters: [{'#e': ['yyy'], since: 444}],
|
|
23
|
+
good: [
|
|
24
|
+
{
|
|
25
|
+
tags: [
|
|
26
|
+
['e', 'uuu'],
|
|
27
|
+
['e', 'yyy']
|
|
28
|
+
],
|
|
29
|
+
created_at: 555
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
bad: [{tags: [['e', 'uuu']], created_at: 111}]
|
|
33
|
+
}
|
|
34
|
+
].forEach(({filters, good, bad}) => {
|
|
35
|
+
good.forEach(ev => {
|
|
36
|
+
expect(matchFilters(filters, ev)).toBeTruthy()
|
|
37
|
+
})
|
|
38
|
+
bad.forEach(ev => {
|
|
39
|
+
expect(matchFilters(filters, ev)).toBeFalsy()
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
})
|
package/{filter.js → filter.ts}
RENAMED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
import {Event} from './event'
|
|
2
|
+
|
|
3
|
+
export type Filter = {
|
|
4
|
+
ids?: string[]
|
|
5
|
+
kinds?: number[]
|
|
6
|
+
authors?: string[]
|
|
7
|
+
since?: number
|
|
8
|
+
until?: number
|
|
9
|
+
[key: `#${string}`]: string[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function matchFilter(
|
|
13
|
+
filter: Filter,
|
|
14
|
+
event: Event & {id: string}
|
|
15
|
+
): boolean {
|
|
2
16
|
if (filter.ids && filter.ids.indexOf(event.id) === -1) return false
|
|
3
17
|
if (filter.kinds && filter.kinds.indexOf(event.kind) === -1) return false
|
|
4
18
|
if (filter.authors && filter.authors.indexOf(event.pubkey) === -1)
|
|
@@ -6,10 +20,12 @@ export function matchFilter(filter, event) {
|
|
|
6
20
|
|
|
7
21
|
for (let f in filter) {
|
|
8
22
|
if (f[0] === '#') {
|
|
23
|
+
let tagName = f.slice(1)
|
|
24
|
+
let values = filter[`#${tagName}`]
|
|
9
25
|
if (
|
|
10
|
-
|
|
26
|
+
values &&
|
|
11
27
|
!event.tags.find(
|
|
12
|
-
([t, v]) => t === f.slice(1) &&
|
|
28
|
+
([t, v]) => t === f.slice(1) && values.indexOf(v) !== -1
|
|
13
29
|
)
|
|
14
30
|
)
|
|
15
31
|
return false
|
|
@@ -22,7 +38,10 @@ export function matchFilter(filter, event) {
|
|
|
22
38
|
return true
|
|
23
39
|
}
|
|
24
40
|
|
|
25
|
-
export function matchFilters(
|
|
41
|
+
export function matchFilters(
|
|
42
|
+
filters: Filter[],
|
|
43
|
+
event: Event & {id: string}
|
|
44
|
+
): boolean {
|
|
26
45
|
for (let i = 0; i < filters.length; i++) {
|
|
27
46
|
if (matchFilter(filters[i], event)) return true
|
|
28
47
|
}
|
package/index.ts
ADDED
package/keys.test.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* eslint-env jest */
|
|
2
|
+
|
|
3
|
+
const {generatePrivateKey, getPublicKey} = require('./lib/nostr.cjs')
|
|
4
|
+
|
|
5
|
+
test('test private key generation', () => {
|
|
6
|
+
expect(generatePrivateKey()).toMatch(/[a-f0-9]{64}/)
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
test('test public key generation', () => {
|
|
10
|
+
expect(getPublicKey(generatePrivateKey())).toMatch(/[a-f0-9]{64}/)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('test public key from private key deterministic', () => {
|
|
14
|
+
let sk = generatePrivateKey()
|
|
15
|
+
let pk = getPublicKey(sk)
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < 5; i++) {
|
|
18
|
+
expect(getPublicKey(sk)).toEqual(pk)
|
|
19
|
+
}
|
|
20
|
+
})
|
package/{keys.js → keys.ts}
RENAMED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as secp256k1 from '@noble/secp256k1'
|
|
2
2
|
import {Buffer} from 'buffer'
|
|
3
3
|
|
|
4
|
-
export function generatePrivateKey() {
|
|
4
|
+
export function generatePrivateKey(): string {
|
|
5
5
|
return Buffer.from(secp256k1.utils.randomPrivateKey()).toString('hex')
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export function getPublicKey(privateKey) {
|
|
8
|
+
export function getPublicKey(privateKey: string): string {
|
|
9
9
|
return Buffer.from(secp256k1.schnorr.getPublicKey(privateKey)).toString('hex')
|
|
10
10
|
}
|