nostr-tools 0.5.0 → 0.6.3
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 +142 -0
- package/.prettierrc.yaml +4 -3
- package/dist/nostr-tools.esm.min.js +411 -0
- package/dist/nostr-tools.umd.min.js +452 -0
- package/event.js +2 -1
- package/index.js +2 -0
- package/nip04.js +37 -0
- package/nip05.js +1 -0
- package/package.json +12 -4
- package/rollup.config.js +16 -0
- package/utils.js +1 -1
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
{
|
|
2
|
+
"parserOptions": {
|
|
3
|
+
"ecmaVersion": 9,
|
|
4
|
+
"ecmaFeatures": {
|
|
5
|
+
"jsx": true
|
|
6
|
+
},
|
|
7
|
+
"sourceType": "module",
|
|
8
|
+
"allowImportExportEverywhere": false
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
"env": {
|
|
12
|
+
"es6": true,
|
|
13
|
+
"node": true
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
"plugins": [
|
|
17
|
+
"babel"
|
|
18
|
+
],
|
|
19
|
+
|
|
20
|
+
"globals": {
|
|
21
|
+
"document": false,
|
|
22
|
+
"navigator": false,
|
|
23
|
+
"window": false,
|
|
24
|
+
"location": false,
|
|
25
|
+
"URL": false,
|
|
26
|
+
"URLSearchParams": false,
|
|
27
|
+
"fetch": false,
|
|
28
|
+
"EventSource": false,
|
|
29
|
+
"localStorage": false,
|
|
30
|
+
"sessionStorage": false
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
"rules": {
|
|
34
|
+
"accessor-pairs": 2,
|
|
35
|
+
"arrow-spacing": [2, { "before": true, "after": true }],
|
|
36
|
+
"block-spacing": [2, "always"],
|
|
37
|
+
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
|
|
38
|
+
"comma-dangle": 0,
|
|
39
|
+
"comma-spacing": [2, { "before": false, "after": true }],
|
|
40
|
+
"comma-style": [2, "last"],
|
|
41
|
+
"constructor-super": 2,
|
|
42
|
+
"curly": [0, "multi-line"],
|
|
43
|
+
"dot-location": [2, "property"],
|
|
44
|
+
"eol-last": 2,
|
|
45
|
+
"eqeqeq": [2, "allow-null"],
|
|
46
|
+
"generator-star-spacing": [2, { "before": true, "after": true }],
|
|
47
|
+
"handle-callback-err": [2, "^(err|error)$" ],
|
|
48
|
+
"indent": 0,
|
|
49
|
+
"jsx-quotes": [2, "prefer-double"],
|
|
50
|
+
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
|
|
51
|
+
"keyword-spacing": [2, { "before": true, "after": true }],
|
|
52
|
+
"new-cap": 0,
|
|
53
|
+
"new-parens": 0,
|
|
54
|
+
"no-array-constructor": 2,
|
|
55
|
+
"no-caller": 2,
|
|
56
|
+
"no-class-assign": 2,
|
|
57
|
+
"no-cond-assign": 2,
|
|
58
|
+
"no-const-assign": 2,
|
|
59
|
+
"no-control-regex": 0,
|
|
60
|
+
"no-debugger": 0,
|
|
61
|
+
"no-delete-var": 2,
|
|
62
|
+
"no-dupe-args": 2,
|
|
63
|
+
"no-dupe-class-members": 2,
|
|
64
|
+
"no-dupe-keys": 2,
|
|
65
|
+
"no-duplicate-case": 2,
|
|
66
|
+
"no-empty-character-class": 2,
|
|
67
|
+
"no-empty-pattern": 2,
|
|
68
|
+
"no-eval": 0,
|
|
69
|
+
"no-ex-assign": 2,
|
|
70
|
+
"no-extend-native": 2,
|
|
71
|
+
"no-extra-bind": 2,
|
|
72
|
+
"no-extra-boolean-cast": 2,
|
|
73
|
+
"no-extra-parens": [2, "functions"],
|
|
74
|
+
"no-fallthrough": 2,
|
|
75
|
+
"no-floating-decimal": 2,
|
|
76
|
+
"no-func-assign": 2,
|
|
77
|
+
"no-implied-eval": 2,
|
|
78
|
+
"no-inner-declarations": [0, "functions"],
|
|
79
|
+
"no-invalid-regexp": 2,
|
|
80
|
+
"no-irregular-whitespace": 2,
|
|
81
|
+
"no-iterator": 2,
|
|
82
|
+
"no-label-var": 2,
|
|
83
|
+
"no-labels": [2, { "allowLoop": false, "allowSwitch": false }],
|
|
84
|
+
"no-lone-blocks": 2,
|
|
85
|
+
"no-mixed-spaces-and-tabs": 2,
|
|
86
|
+
"no-multi-spaces": 2,
|
|
87
|
+
"no-multi-str": 2,
|
|
88
|
+
"no-multiple-empty-lines": [2, { "max": 2 }],
|
|
89
|
+
"no-native-reassign": 2,
|
|
90
|
+
"no-negated-in-lhs": 2,
|
|
91
|
+
"no-new": 0,
|
|
92
|
+
"no-new-func": 2,
|
|
93
|
+
"no-new-object": 2,
|
|
94
|
+
"no-new-require": 2,
|
|
95
|
+
"no-new-symbol": 2,
|
|
96
|
+
"no-new-wrappers": 2,
|
|
97
|
+
"no-obj-calls": 2,
|
|
98
|
+
"no-octal": 2,
|
|
99
|
+
"no-octal-escape": 2,
|
|
100
|
+
"no-path-concat": 0,
|
|
101
|
+
"no-proto": 2,
|
|
102
|
+
"no-redeclare": 2,
|
|
103
|
+
"no-regex-spaces": 2,
|
|
104
|
+
"no-return-assign": 0,
|
|
105
|
+
"no-self-assign": 2,
|
|
106
|
+
"no-self-compare": 2,
|
|
107
|
+
"no-sequences": 2,
|
|
108
|
+
"no-shadow-restricted-names": 2,
|
|
109
|
+
"no-spaced-func": 2,
|
|
110
|
+
"no-sparse-arrays": 2,
|
|
111
|
+
"no-this-before-super": 2,
|
|
112
|
+
"no-throw-literal": 2,
|
|
113
|
+
"no-trailing-spaces": 2,
|
|
114
|
+
"no-undef": 2,
|
|
115
|
+
"no-undef-init": 2,
|
|
116
|
+
"no-unexpected-multiline": 2,
|
|
117
|
+
"no-unneeded-ternary": [2, { "defaultAssignment": false }],
|
|
118
|
+
"no-unreachable": 2,
|
|
119
|
+
"no-unused-vars": [2, { "vars": "local", "args": "none", "varsIgnorePattern": "^_"}],
|
|
120
|
+
"no-useless-call": 2,
|
|
121
|
+
"no-useless-constructor": 2,
|
|
122
|
+
"no-with": 2,
|
|
123
|
+
"one-var": [0, { "initialized": "never" }],
|
|
124
|
+
"operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }],
|
|
125
|
+
"padded-blocks": [2, "never"],
|
|
126
|
+
"quotes": [2, "single", { "avoidEscape": true, "allowTemplateLiterals": true }],
|
|
127
|
+
"semi": [2, "never"],
|
|
128
|
+
"semi-spacing": [2, { "before": false, "after": true }],
|
|
129
|
+
"space-before-blocks": [2, "always"],
|
|
130
|
+
"space-before-function-paren": 0,
|
|
131
|
+
"space-in-parens": [2, "never"],
|
|
132
|
+
"space-infix-ops": 2,
|
|
133
|
+
"space-unary-ops": [2, { "words": true, "nonwords": false }],
|
|
134
|
+
"spaced-comment": 0,
|
|
135
|
+
"template-curly-spacing": [2, "never"],
|
|
136
|
+
"use-isnan": 2,
|
|
137
|
+
"valid-typeof": 2,
|
|
138
|
+
"wrap-iife": [2, "any"],
|
|
139
|
+
"yield-star-spacing": [2, "both"],
|
|
140
|
+
"yoda": [0]
|
|
141
|
+
}
|
|
142
|
+
}
|
package/.prettierrc.yaml
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
semi: false
|
|
1
2
|
arrowParens: avoid
|
|
2
|
-
|
|
3
|
-
jsxBracketSameLine: false
|
|
3
|
+
insertPragma: false
|
|
4
4
|
printWidth: 80
|
|
5
5
|
proseWrap: preserve
|
|
6
|
-
semi: false
|
|
7
6
|
singleQuote: true
|
|
8
7
|
trailingComma: none
|
|
9
8
|
useTabs: false
|
|
9
|
+
jsxBracketSameLine: false
|
|
10
|
+
bracketSpacing: false
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import 'websocket-polyfill';
|
|
2
|
+
import Buffer from 'buffer';
|
|
3
|
+
import * as secp256k1 from '@noble/secp256k1';
|
|
4
|
+
import dnsPacket from 'dns-packet';
|
|
5
|
+
|
|
6
|
+
const makeRandom32 = () => secp256k1.utils.randomPrivateKey();
|
|
7
|
+
const sha256 = m => secp256k1.utils.sha256(Uint8Array.from(m));
|
|
8
|
+
const getPublicKey = privateKey =>
|
|
9
|
+
secp256k1.schnorr.getPublicKey(privateKey);
|
|
10
|
+
|
|
11
|
+
function getBlankEvent() {
|
|
12
|
+
return {
|
|
13
|
+
kind: 255,
|
|
14
|
+
pubkey: null,
|
|
15
|
+
content: '',
|
|
16
|
+
tags: [],
|
|
17
|
+
created_at: 0
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function serializeEvent(evt) {
|
|
22
|
+
return JSON.stringify([
|
|
23
|
+
0,
|
|
24
|
+
evt.pubkey,
|
|
25
|
+
evt.created_at,
|
|
26
|
+
evt.kind,
|
|
27
|
+
evt.tags || [],
|
|
28
|
+
evt.content
|
|
29
|
+
])
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function getEventHash(event) {
|
|
33
|
+
let eventHash = await sha256(Buffer.from(serializeEvent(event)));
|
|
34
|
+
return Buffer.from(eventHash).toString('hex')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function verifySignature(event) {
|
|
38
|
+
return await secp256k1.schnorr.verify(
|
|
39
|
+
event.sig,
|
|
40
|
+
await getEventHash(event),
|
|
41
|
+
event.pubkey
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function signEvent(event, key) {
|
|
46
|
+
let eventHash = await getEventHash(event);
|
|
47
|
+
return await secp256k1.schnorr.sign(eventHash, key)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeRelayURL(url) {
|
|
51
|
+
let [host, ...qs] = url.split('?');
|
|
52
|
+
if (host.slice(0, 4) === 'http') host = 'ws' + host.slice(4);
|
|
53
|
+
if (host.slice(0, 2) !== 'ws') host = 'wss://' + host;
|
|
54
|
+
if (host.length && host[host.length - 1] === '/') host = host.slice(0, -1);
|
|
55
|
+
return [host, ...qs].join('?')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function relayConnect(url, onNotice) {
|
|
59
|
+
url = normalizeRelayURL(url);
|
|
60
|
+
|
|
61
|
+
var ws, resolveOpen, untilOpen;
|
|
62
|
+
var openSubs = {};
|
|
63
|
+
let attemptNumber = 1;
|
|
64
|
+
let nextAttemptSeconds = 1;
|
|
65
|
+
|
|
66
|
+
function resetOpenState() {
|
|
67
|
+
untilOpen = new Promise(resolve => {
|
|
68
|
+
resolveOpen = resolve;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
var channels = {};
|
|
73
|
+
|
|
74
|
+
function connect() {
|
|
75
|
+
ws = new WebSocket(url);
|
|
76
|
+
|
|
77
|
+
ws.onopen = () => {
|
|
78
|
+
console.log('connected to', url);
|
|
79
|
+
resolveOpen();
|
|
80
|
+
|
|
81
|
+
// restablish old subscriptions
|
|
82
|
+
for (let channel in openSubs) {
|
|
83
|
+
let filters = openSubs[channel];
|
|
84
|
+
let cb = channels[channel];
|
|
85
|
+
sub({cb, filter: filters}, channel);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
ws.onerror = () => {
|
|
89
|
+
console.log('error connecting to relay', url);
|
|
90
|
+
};
|
|
91
|
+
ws.onclose = () => {
|
|
92
|
+
resetOpenState();
|
|
93
|
+
attemptNumber++;
|
|
94
|
+
nextAttemptSeconds += attemptNumber;
|
|
95
|
+
console.log(
|
|
96
|
+
`relay ${url} connection closed. reconnecting in ${nextAttemptSeconds} seconds.`
|
|
97
|
+
);
|
|
98
|
+
setTimeout(async () => {
|
|
99
|
+
try {
|
|
100
|
+
connect();
|
|
101
|
+
} catch (err) {}
|
|
102
|
+
}, nextAttemptSeconds * 1000);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
ws.onmessage = async e => {
|
|
106
|
+
var data;
|
|
107
|
+
try {
|
|
108
|
+
data = JSON.parse(e.data);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
data = e.data;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (data.length > 1) {
|
|
114
|
+
if (data[0] === 'NOTICE') {
|
|
115
|
+
if (data.length < 2) return
|
|
116
|
+
|
|
117
|
+
console.log('message from relay ' + url + ': ' + data[1]);
|
|
118
|
+
onNotice(data[1]);
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (data[0] === 'EVENT') {
|
|
123
|
+
if (data.length < 3) return
|
|
124
|
+
|
|
125
|
+
let channel = data[1];
|
|
126
|
+
let event = data[2];
|
|
127
|
+
|
|
128
|
+
if (await verifySignature(event)) {
|
|
129
|
+
if (channels[channel]) {
|
|
130
|
+
channels[channel](event);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
console.warn('got event with invalid signature from ' + url, event);
|
|
134
|
+
}
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
resetOpenState();
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
connect();
|
|
145
|
+
} catch (err) {}
|
|
146
|
+
|
|
147
|
+
async function trySend(params) {
|
|
148
|
+
let msg = JSON.stringify(params);
|
|
149
|
+
|
|
150
|
+
await untilOpen;
|
|
151
|
+
ws.send(msg);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const sub = ({cb, filter}, channel = Math.random().toString().slice(2)) => {
|
|
155
|
+
var filters = [];
|
|
156
|
+
if (Array.isArray(filter)) {
|
|
157
|
+
filters = filter;
|
|
158
|
+
} else {
|
|
159
|
+
filters.push(filter);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
trySend(['REQ', channel, ...filters]);
|
|
163
|
+
channels[channel] = cb;
|
|
164
|
+
openSubs[channel] = filters;
|
|
165
|
+
|
|
166
|
+
const activeCallback = cb;
|
|
167
|
+
const activeFilters = filters;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
sub: ({cb = activeCallback, filter = activeFilters}) =>
|
|
171
|
+
sub({cb, filter}, channel),
|
|
172
|
+
unsub: () => {
|
|
173
|
+
delete openSubs[channel];
|
|
174
|
+
delete channels[channel];
|
|
175
|
+
trySend(['CLOSE', channel]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
url,
|
|
182
|
+
sub,
|
|
183
|
+
async publish(event, statusCallback = status => {}) {
|
|
184
|
+
try {
|
|
185
|
+
await trySend(['EVENT', event]);
|
|
186
|
+
statusCallback(0);
|
|
187
|
+
let {unsub} = relay.sub({
|
|
188
|
+
cb: () => {
|
|
189
|
+
statusCallback(1);
|
|
190
|
+
},
|
|
191
|
+
filter: {id: event.id}
|
|
192
|
+
});
|
|
193
|
+
setTimeout(unsub, 5000);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
statusCallback(-1);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
close() {
|
|
199
|
+
ws.close();
|
|
200
|
+
},
|
|
201
|
+
get status() {
|
|
202
|
+
return ws.readyState
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function relayPool(globalPrivateKey) {
|
|
208
|
+
const relays = {};
|
|
209
|
+
const noticeCallbacks = [];
|
|
210
|
+
|
|
211
|
+
function propagateNotice(notice, relayURL) {
|
|
212
|
+
for (let i = 0; i < noticeCallbacks.length; i++) {
|
|
213
|
+
let {relay} = relays[relayURL];
|
|
214
|
+
noticeCallbacks[i](notice, relay);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const activeSubscriptions = {};
|
|
219
|
+
|
|
220
|
+
const sub = ({cb, filter}, id = Math.random().toString().slice(2)) => {
|
|
221
|
+
const subControllers = Object.fromEntries(
|
|
222
|
+
Object.values(relays)
|
|
223
|
+
.filter(({policy}) => policy.read)
|
|
224
|
+
.map(({relay}) => [
|
|
225
|
+
relay.url,
|
|
226
|
+
relay.sub({filter, cb: event => cb(event, relay.url)})
|
|
227
|
+
])
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const activeCallback = cb;
|
|
231
|
+
const activeFilters = filter;
|
|
232
|
+
|
|
233
|
+
activeSubscriptions[id] = {
|
|
234
|
+
sub: ({cb = activeCallback, filter = activeFilters}) =>
|
|
235
|
+
Object.entries(subControllers).map(([relayURL, sub]) => [
|
|
236
|
+
relayURL,
|
|
237
|
+
sub.sub({cb, filter}, id)
|
|
238
|
+
]),
|
|
239
|
+
addRelay: relay => {
|
|
240
|
+
subControllers[relay.url] = relay.sub({cb, filter});
|
|
241
|
+
},
|
|
242
|
+
removeRelay: relayURL => {
|
|
243
|
+
if (relayURL in subControllers) {
|
|
244
|
+
subControllers[relayURL].unsub();
|
|
245
|
+
if (Object.keys(subControllers).length === 0) unsub();
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
unsub: () => {
|
|
249
|
+
Object.values(subControllers).forEach(sub => sub.unsub());
|
|
250
|
+
delete activeSubscriptions[id];
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
return activeSubscriptions[id]
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
sub,
|
|
259
|
+
relays,
|
|
260
|
+
setPrivateKey(privateKey) {
|
|
261
|
+
globalPrivateKey = privateKey;
|
|
262
|
+
},
|
|
263
|
+
async addRelay(url, policy = {read: true, write: true}) {
|
|
264
|
+
let relayURL = normalizeRelayURL(url);
|
|
265
|
+
if (relayURL in relays) return
|
|
266
|
+
|
|
267
|
+
let relay = await relayConnect(url, notice => {
|
|
268
|
+
propagateNotice(notice, relayURL);
|
|
269
|
+
});
|
|
270
|
+
relays[relayURL] = {relay, policy};
|
|
271
|
+
|
|
272
|
+
Object.values(activeSubscriptions).forEach(subscription =>
|
|
273
|
+
subscription.addRelay(relay)
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
return relay
|
|
277
|
+
},
|
|
278
|
+
removeRelay(url) {
|
|
279
|
+
let relayURL = normalizeRelayURL(url);
|
|
280
|
+
let {relay} = relays[relayURL];
|
|
281
|
+
if (!relay) return
|
|
282
|
+
Object.values(activeSubscriptions).forEach(subscription =>
|
|
283
|
+
subscription.removeRelay(relay)
|
|
284
|
+
);
|
|
285
|
+
relay.close();
|
|
286
|
+
delete relays[relayURL];
|
|
287
|
+
},
|
|
288
|
+
onNotice(cb) {
|
|
289
|
+
noticeCallbacks.push(cb);
|
|
290
|
+
},
|
|
291
|
+
offNotice(cb) {
|
|
292
|
+
let index = noticeCallbacks.indexOf(cb);
|
|
293
|
+
if (index !== -1) noticeCallbacks.splice(index, 1);
|
|
294
|
+
},
|
|
295
|
+
async publish(event, statusCallback = (status, relayURL) => {}) {
|
|
296
|
+
if (!event.sig) {
|
|
297
|
+
event.tags = event.tags || [];
|
|
298
|
+
|
|
299
|
+
if (globalPrivateKey) {
|
|
300
|
+
event.id = await getEventHash(event);
|
|
301
|
+
event.sig = await signEvent(event, globalPrivateKey);
|
|
302
|
+
} else {
|
|
303
|
+
throw new Error(
|
|
304
|
+
"can't publish unsigned event. either sign this event beforehand or pass a private key while initializing this relay pool so it can be signed automatically."
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
Object.values(relays)
|
|
310
|
+
.filter(({policy}) => policy.write)
|
|
311
|
+
.map(async ({relay}) => {
|
|
312
|
+
try {
|
|
313
|
+
await relay.publish(event, status =>
|
|
314
|
+
statusCallback(status, relay.url)
|
|
315
|
+
);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
statusCallback(-1, relay.url);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
return event
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function encrypt(privkey, pubkey, text) {
|
|
327
|
+
const key = secp256k1.getSharedSecret(privkey, '02' + pubkey);
|
|
328
|
+
const normalizedKey = getOnlyXFromFullSharedSecret(key);
|
|
329
|
+
|
|
330
|
+
let iv = crypto.randomFillSync(new Uint8Array(16));
|
|
331
|
+
var cipher = crypto.createCipheriv(
|
|
332
|
+
'aes-256-cbc',
|
|
333
|
+
Buffer.from(normalizedKey, 'hex'),
|
|
334
|
+
iv
|
|
335
|
+
);
|
|
336
|
+
let encryptedMessage = cipher.update(text, 'utf8', 'base64');
|
|
337
|
+
encryptedMessage += cipher.final('base64');
|
|
338
|
+
|
|
339
|
+
return [encryptedMessage, Buffer.from(iv.buffer).toString('base64')]
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function decrypt(privkey, pubkey, ciphertext, iv) {
|
|
343
|
+
const key = secp256k1.getSharedSecret(privkey, '02' + pubkey);
|
|
344
|
+
const normalizedKey = getOnlyXFromFullSharedSecret(key);
|
|
345
|
+
|
|
346
|
+
var decipher = crypto.createDecipheriv(
|
|
347
|
+
'aes-256-cbc',
|
|
348
|
+
Buffer.from(normalizedKey, 'hex'),
|
|
349
|
+
Buffer.from(iv, 'base64')
|
|
350
|
+
);
|
|
351
|
+
let decryptedMessage = decipher.update(ciphertext, 'base64');
|
|
352
|
+
decryptedMessage += decipher.final('utf8');
|
|
353
|
+
|
|
354
|
+
return decryptedMessage
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function getOnlyXFromFullSharedSecret(fullSharedSecretCoordinates) {
|
|
358
|
+
return fullSharedSecretCoordinates.substr(2, 64)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const dohProviders = [
|
|
362
|
+
'cloudflare-dns.com',
|
|
363
|
+
'fi.doh.dns.snopyta.org',
|
|
364
|
+
'basic.bravedns.com',
|
|
365
|
+
'hydra.plan9-ns1.com',
|
|
366
|
+
'doh.pl.ahadns.net',
|
|
367
|
+
'dns.flatuslifir.is',
|
|
368
|
+
'doh.dns.sb',
|
|
369
|
+
'doh.li'
|
|
370
|
+
];
|
|
371
|
+
|
|
372
|
+
let counter = 0;
|
|
373
|
+
|
|
374
|
+
async function keyFromDomain(domain) {
|
|
375
|
+
let host = dohProviders[counter % dohProviders.length];
|
|
376
|
+
|
|
377
|
+
let buf = dnsPacket.encode({
|
|
378
|
+
type: 'query',
|
|
379
|
+
id: Math.floor(Math.random() * 65534),
|
|
380
|
+
flags: dnsPacket.RECURSION_DESIRED,
|
|
381
|
+
questions: [
|
|
382
|
+
{
|
|
383
|
+
type: 'TXT',
|
|
384
|
+
name: `_nostrkey.${domain}`
|
|
385
|
+
}
|
|
386
|
+
]
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
let fetching = fetch(`https://${host}/dns-query`, {
|
|
390
|
+
method: 'POST',
|
|
391
|
+
headers: {
|
|
392
|
+
'Content-Type': 'application/dns-message',
|
|
393
|
+
'Content-Length': Buffer.byteLength(buf)
|
|
394
|
+
},
|
|
395
|
+
body: buf
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
counter++;
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
let response = Buffer.from(await (await fetching).arrayBuffer());
|
|
402
|
+
let {answers} = dnsPacket.decode(response);
|
|
403
|
+
if (answers.length === 0) return null
|
|
404
|
+
return Buffer.from(answers[0].data[0]).toString()
|
|
405
|
+
} catch (err) {
|
|
406
|
+
console.log(`error querying DNS for ${domain} on ${host}`, err);
|
|
407
|
+
return null
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export { decrypt, encrypt, getBlankEvent, getEventHash, getPublicKey, keyFromDomain, makeRandom32, relayConnect, relayPool, serializeEvent, sha256, signEvent, verifySignature };
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('websocket-polyfill'), require('buffer'), require('@noble/secp256k1'), require('dns-packet')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', 'websocket-polyfill', 'buffer', '@noble/secp256k1', 'dns-packet'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.nostrtools = {}, null, global.Buffer, global.secp256k1, global.dnsPacket));
|
|
5
|
+
})(this, (function (exports, websocketPolyfill, Buffer, secp256k1, dnsPacket) { 'use strict';
|
|
6
|
+
|
|
7
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
8
|
+
|
|
9
|
+
function _interopNamespace(e) {
|
|
10
|
+
if (e && e.__esModule) return e;
|
|
11
|
+
var n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
Object.keys(e).forEach(function (k) {
|
|
14
|
+
if (k !== 'default') {
|
|
15
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () { return e[k]; }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
n["default"] = e;
|
|
24
|
+
return Object.freeze(n);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var Buffer__default = /*#__PURE__*/_interopDefaultLegacy(Buffer);
|
|
28
|
+
var secp256k1__namespace = /*#__PURE__*/_interopNamespace(secp256k1);
|
|
29
|
+
var dnsPacket__default = /*#__PURE__*/_interopDefaultLegacy(dnsPacket);
|
|
30
|
+
|
|
31
|
+
const makeRandom32 = () => secp256k1__namespace.utils.randomPrivateKey();
|
|
32
|
+
const sha256 = m => secp256k1__namespace.utils.sha256(Uint8Array.from(m));
|
|
33
|
+
const getPublicKey = privateKey =>
|
|
34
|
+
secp256k1__namespace.schnorr.getPublicKey(privateKey);
|
|
35
|
+
|
|
36
|
+
function getBlankEvent() {
|
|
37
|
+
return {
|
|
38
|
+
kind: 255,
|
|
39
|
+
pubkey: null,
|
|
40
|
+
content: '',
|
|
41
|
+
tags: [],
|
|
42
|
+
created_at: 0
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function serializeEvent(evt) {
|
|
47
|
+
return JSON.stringify([
|
|
48
|
+
0,
|
|
49
|
+
evt.pubkey,
|
|
50
|
+
evt.created_at,
|
|
51
|
+
evt.kind,
|
|
52
|
+
evt.tags || [],
|
|
53
|
+
evt.content
|
|
54
|
+
])
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function getEventHash(event) {
|
|
58
|
+
let eventHash = await sha256(Buffer__default["default"].from(serializeEvent(event)));
|
|
59
|
+
return Buffer__default["default"].from(eventHash).toString('hex')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function verifySignature(event) {
|
|
63
|
+
return await secp256k1__namespace.schnorr.verify(
|
|
64
|
+
event.sig,
|
|
65
|
+
await getEventHash(event),
|
|
66
|
+
event.pubkey
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function signEvent(event, key) {
|
|
71
|
+
let eventHash = await getEventHash(event);
|
|
72
|
+
return await secp256k1__namespace.schnorr.sign(eventHash, key)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeRelayURL(url) {
|
|
76
|
+
let [host, ...qs] = url.split('?');
|
|
77
|
+
if (host.slice(0, 4) === 'http') host = 'ws' + host.slice(4);
|
|
78
|
+
if (host.slice(0, 2) !== 'ws') host = 'wss://' + host;
|
|
79
|
+
if (host.length && host[host.length - 1] === '/') host = host.slice(0, -1);
|
|
80
|
+
return [host, ...qs].join('?')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function relayConnect(url, onNotice) {
|
|
84
|
+
url = normalizeRelayURL(url);
|
|
85
|
+
|
|
86
|
+
var ws, resolveOpen, untilOpen;
|
|
87
|
+
var openSubs = {};
|
|
88
|
+
let attemptNumber = 1;
|
|
89
|
+
let nextAttemptSeconds = 1;
|
|
90
|
+
|
|
91
|
+
function resetOpenState() {
|
|
92
|
+
untilOpen = new Promise(resolve => {
|
|
93
|
+
resolveOpen = resolve;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
var channels = {};
|
|
98
|
+
|
|
99
|
+
function connect() {
|
|
100
|
+
ws = new WebSocket(url);
|
|
101
|
+
|
|
102
|
+
ws.onopen = () => {
|
|
103
|
+
console.log('connected to', url);
|
|
104
|
+
resolveOpen();
|
|
105
|
+
|
|
106
|
+
// restablish old subscriptions
|
|
107
|
+
for (let channel in openSubs) {
|
|
108
|
+
let filters = openSubs[channel];
|
|
109
|
+
let cb = channels[channel];
|
|
110
|
+
sub({cb, filter: filters}, channel);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
ws.onerror = () => {
|
|
114
|
+
console.log('error connecting to relay', url);
|
|
115
|
+
};
|
|
116
|
+
ws.onclose = () => {
|
|
117
|
+
resetOpenState();
|
|
118
|
+
attemptNumber++;
|
|
119
|
+
nextAttemptSeconds += attemptNumber;
|
|
120
|
+
console.log(
|
|
121
|
+
`relay ${url} connection closed. reconnecting in ${nextAttemptSeconds} seconds.`
|
|
122
|
+
);
|
|
123
|
+
setTimeout(async () => {
|
|
124
|
+
try {
|
|
125
|
+
connect();
|
|
126
|
+
} catch (err) {}
|
|
127
|
+
}, nextAttemptSeconds * 1000);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
ws.onmessage = async e => {
|
|
131
|
+
var data;
|
|
132
|
+
try {
|
|
133
|
+
data = JSON.parse(e.data);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
data = e.data;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (data.length > 1) {
|
|
139
|
+
if (data[0] === 'NOTICE') {
|
|
140
|
+
if (data.length < 2) return
|
|
141
|
+
|
|
142
|
+
console.log('message from relay ' + url + ': ' + data[1]);
|
|
143
|
+
onNotice(data[1]);
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (data[0] === 'EVENT') {
|
|
148
|
+
if (data.length < 3) return
|
|
149
|
+
|
|
150
|
+
let channel = data[1];
|
|
151
|
+
let event = data[2];
|
|
152
|
+
|
|
153
|
+
if (await verifySignature(event)) {
|
|
154
|
+
if (channels[channel]) {
|
|
155
|
+
channels[channel](event);
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
console.warn('got event with invalid signature from ' + url, event);
|
|
159
|
+
}
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
resetOpenState();
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
connect();
|
|
170
|
+
} catch (err) {}
|
|
171
|
+
|
|
172
|
+
async function trySend(params) {
|
|
173
|
+
let msg = JSON.stringify(params);
|
|
174
|
+
|
|
175
|
+
await untilOpen;
|
|
176
|
+
ws.send(msg);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const sub = ({cb, filter}, channel = Math.random().toString().slice(2)) => {
|
|
180
|
+
var filters = [];
|
|
181
|
+
if (Array.isArray(filter)) {
|
|
182
|
+
filters = filter;
|
|
183
|
+
} else {
|
|
184
|
+
filters.push(filter);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
trySend(['REQ', channel, ...filters]);
|
|
188
|
+
channels[channel] = cb;
|
|
189
|
+
openSubs[channel] = filters;
|
|
190
|
+
|
|
191
|
+
const activeCallback = cb;
|
|
192
|
+
const activeFilters = filters;
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
sub: ({cb = activeCallback, filter = activeFilters}) =>
|
|
196
|
+
sub({cb, filter}, channel),
|
|
197
|
+
unsub: () => {
|
|
198
|
+
delete openSubs[channel];
|
|
199
|
+
delete channels[channel];
|
|
200
|
+
trySend(['CLOSE', channel]);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
url,
|
|
207
|
+
sub,
|
|
208
|
+
async publish(event, statusCallback = status => {}) {
|
|
209
|
+
try {
|
|
210
|
+
await trySend(['EVENT', event]);
|
|
211
|
+
statusCallback(0);
|
|
212
|
+
let {unsub} = relay.sub({
|
|
213
|
+
cb: () => {
|
|
214
|
+
statusCallback(1);
|
|
215
|
+
},
|
|
216
|
+
filter: {id: event.id}
|
|
217
|
+
});
|
|
218
|
+
setTimeout(unsub, 5000);
|
|
219
|
+
} catch (err) {
|
|
220
|
+
statusCallback(-1);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
close() {
|
|
224
|
+
ws.close();
|
|
225
|
+
},
|
|
226
|
+
get status() {
|
|
227
|
+
return ws.readyState
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function relayPool(globalPrivateKey) {
|
|
233
|
+
const relays = {};
|
|
234
|
+
const noticeCallbacks = [];
|
|
235
|
+
|
|
236
|
+
function propagateNotice(notice, relayURL) {
|
|
237
|
+
for (let i = 0; i < noticeCallbacks.length; i++) {
|
|
238
|
+
let {relay} = relays[relayURL];
|
|
239
|
+
noticeCallbacks[i](notice, relay);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const activeSubscriptions = {};
|
|
244
|
+
|
|
245
|
+
const sub = ({cb, filter}, id = Math.random().toString().slice(2)) => {
|
|
246
|
+
const subControllers = Object.fromEntries(
|
|
247
|
+
Object.values(relays)
|
|
248
|
+
.filter(({policy}) => policy.read)
|
|
249
|
+
.map(({relay}) => [
|
|
250
|
+
relay.url,
|
|
251
|
+
relay.sub({filter, cb: event => cb(event, relay.url)})
|
|
252
|
+
])
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const activeCallback = cb;
|
|
256
|
+
const activeFilters = filter;
|
|
257
|
+
|
|
258
|
+
activeSubscriptions[id] = {
|
|
259
|
+
sub: ({cb = activeCallback, filter = activeFilters}) =>
|
|
260
|
+
Object.entries(subControllers).map(([relayURL, sub]) => [
|
|
261
|
+
relayURL,
|
|
262
|
+
sub.sub({cb, filter}, id)
|
|
263
|
+
]),
|
|
264
|
+
addRelay: relay => {
|
|
265
|
+
subControllers[relay.url] = relay.sub({cb, filter});
|
|
266
|
+
},
|
|
267
|
+
removeRelay: relayURL => {
|
|
268
|
+
if (relayURL in subControllers) {
|
|
269
|
+
subControllers[relayURL].unsub();
|
|
270
|
+
if (Object.keys(subControllers).length === 0) unsub();
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
unsub: () => {
|
|
274
|
+
Object.values(subControllers).forEach(sub => sub.unsub());
|
|
275
|
+
delete activeSubscriptions[id];
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
return activeSubscriptions[id]
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
sub,
|
|
284
|
+
relays,
|
|
285
|
+
setPrivateKey(privateKey) {
|
|
286
|
+
globalPrivateKey = privateKey;
|
|
287
|
+
},
|
|
288
|
+
async addRelay(url, policy = {read: true, write: true}) {
|
|
289
|
+
let relayURL = normalizeRelayURL(url);
|
|
290
|
+
if (relayURL in relays) return
|
|
291
|
+
|
|
292
|
+
let relay = await relayConnect(url, notice => {
|
|
293
|
+
propagateNotice(notice, relayURL);
|
|
294
|
+
});
|
|
295
|
+
relays[relayURL] = {relay, policy};
|
|
296
|
+
|
|
297
|
+
Object.values(activeSubscriptions).forEach(subscription =>
|
|
298
|
+
subscription.addRelay(relay)
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
return relay
|
|
302
|
+
},
|
|
303
|
+
removeRelay(url) {
|
|
304
|
+
let relayURL = normalizeRelayURL(url);
|
|
305
|
+
let {relay} = relays[relayURL];
|
|
306
|
+
if (!relay) return
|
|
307
|
+
Object.values(activeSubscriptions).forEach(subscription =>
|
|
308
|
+
subscription.removeRelay(relay)
|
|
309
|
+
);
|
|
310
|
+
relay.close();
|
|
311
|
+
delete relays[relayURL];
|
|
312
|
+
},
|
|
313
|
+
onNotice(cb) {
|
|
314
|
+
noticeCallbacks.push(cb);
|
|
315
|
+
},
|
|
316
|
+
offNotice(cb) {
|
|
317
|
+
let index = noticeCallbacks.indexOf(cb);
|
|
318
|
+
if (index !== -1) noticeCallbacks.splice(index, 1);
|
|
319
|
+
},
|
|
320
|
+
async publish(event, statusCallback = (status, relayURL) => {}) {
|
|
321
|
+
if (!event.sig) {
|
|
322
|
+
event.tags = event.tags || [];
|
|
323
|
+
|
|
324
|
+
if (globalPrivateKey) {
|
|
325
|
+
event.id = await getEventHash(event);
|
|
326
|
+
event.sig = await signEvent(event, globalPrivateKey);
|
|
327
|
+
} else {
|
|
328
|
+
throw new Error(
|
|
329
|
+
"can't publish unsigned event. either sign this event beforehand or pass a private key while initializing this relay pool so it can be signed automatically."
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
Object.values(relays)
|
|
335
|
+
.filter(({policy}) => policy.write)
|
|
336
|
+
.map(async ({relay}) => {
|
|
337
|
+
try {
|
|
338
|
+
await relay.publish(event, status =>
|
|
339
|
+
statusCallback(status, relay.url)
|
|
340
|
+
);
|
|
341
|
+
} catch (err) {
|
|
342
|
+
statusCallback(-1, relay.url);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
return event
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function encrypt(privkey, pubkey, text) {
|
|
352
|
+
const key = secp256k1__namespace.getSharedSecret(privkey, '02' + pubkey);
|
|
353
|
+
const normalizedKey = getOnlyXFromFullSharedSecret(key);
|
|
354
|
+
|
|
355
|
+
let iv = crypto.randomFillSync(new Uint8Array(16));
|
|
356
|
+
var cipher = crypto.createCipheriv(
|
|
357
|
+
'aes-256-cbc',
|
|
358
|
+
Buffer__default["default"].from(normalizedKey, 'hex'),
|
|
359
|
+
iv
|
|
360
|
+
);
|
|
361
|
+
let encryptedMessage = cipher.update(text, 'utf8', 'base64');
|
|
362
|
+
encryptedMessage += cipher.final('base64');
|
|
363
|
+
|
|
364
|
+
return [encryptedMessage, Buffer__default["default"].from(iv.buffer).toString('base64')]
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function decrypt(privkey, pubkey, ciphertext, iv) {
|
|
368
|
+
const key = secp256k1__namespace.getSharedSecret(privkey, '02' + pubkey);
|
|
369
|
+
const normalizedKey = getOnlyXFromFullSharedSecret(key);
|
|
370
|
+
|
|
371
|
+
var decipher = crypto.createDecipheriv(
|
|
372
|
+
'aes-256-cbc',
|
|
373
|
+
Buffer__default["default"].from(normalizedKey, 'hex'),
|
|
374
|
+
Buffer__default["default"].from(iv, 'base64')
|
|
375
|
+
);
|
|
376
|
+
let decryptedMessage = decipher.update(ciphertext, 'base64');
|
|
377
|
+
decryptedMessage += decipher.final('utf8');
|
|
378
|
+
|
|
379
|
+
return decryptedMessage
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function getOnlyXFromFullSharedSecret(fullSharedSecretCoordinates) {
|
|
383
|
+
return fullSharedSecretCoordinates.substr(2, 64)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const dohProviders = [
|
|
387
|
+
'cloudflare-dns.com',
|
|
388
|
+
'fi.doh.dns.snopyta.org',
|
|
389
|
+
'basic.bravedns.com',
|
|
390
|
+
'hydra.plan9-ns1.com',
|
|
391
|
+
'doh.pl.ahadns.net',
|
|
392
|
+
'dns.flatuslifir.is',
|
|
393
|
+
'doh.dns.sb',
|
|
394
|
+
'doh.li'
|
|
395
|
+
];
|
|
396
|
+
|
|
397
|
+
let counter = 0;
|
|
398
|
+
|
|
399
|
+
async function keyFromDomain(domain) {
|
|
400
|
+
let host = dohProviders[counter % dohProviders.length];
|
|
401
|
+
|
|
402
|
+
let buf = dnsPacket__default["default"].encode({
|
|
403
|
+
type: 'query',
|
|
404
|
+
id: Math.floor(Math.random() * 65534),
|
|
405
|
+
flags: dnsPacket__default["default"].RECURSION_DESIRED,
|
|
406
|
+
questions: [
|
|
407
|
+
{
|
|
408
|
+
type: 'TXT',
|
|
409
|
+
name: `_nostrkey.${domain}`
|
|
410
|
+
}
|
|
411
|
+
]
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
let fetching = fetch(`https://${host}/dns-query`, {
|
|
415
|
+
method: 'POST',
|
|
416
|
+
headers: {
|
|
417
|
+
'Content-Type': 'application/dns-message',
|
|
418
|
+
'Content-Length': Buffer__default["default"].byteLength(buf)
|
|
419
|
+
},
|
|
420
|
+
body: buf
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
counter++;
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
let response = Buffer__default["default"].from(await (await fetching).arrayBuffer());
|
|
427
|
+
let {answers} = dnsPacket__default["default"].decode(response);
|
|
428
|
+
if (answers.length === 0) return null
|
|
429
|
+
return Buffer__default["default"].from(answers[0].data[0]).toString()
|
|
430
|
+
} catch (err) {
|
|
431
|
+
console.log(`error querying DNS for ${domain} on ${host}`, err);
|
|
432
|
+
return null
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
exports.decrypt = decrypt;
|
|
437
|
+
exports.encrypt = encrypt;
|
|
438
|
+
exports.getBlankEvent = getBlankEvent;
|
|
439
|
+
exports.getEventHash = getEventHash;
|
|
440
|
+
exports.getPublicKey = getPublicKey;
|
|
441
|
+
exports.keyFromDomain = keyFromDomain;
|
|
442
|
+
exports.makeRandom32 = makeRandom32;
|
|
443
|
+
exports.relayConnect = relayConnect;
|
|
444
|
+
exports.relayPool = relayPool;
|
|
445
|
+
exports.serializeEvent = serializeEvent;
|
|
446
|
+
exports.sha256 = sha256;
|
|
447
|
+
exports.signEvent = signEvent;
|
|
448
|
+
exports.verifySignature = verifySignature;
|
|
449
|
+
|
|
450
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
451
|
+
|
|
452
|
+
}));
|
package/event.js
CHANGED
package/index.js
CHANGED
package/nip04.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import Buffer from 'buffer'
|
|
2
|
+
import * as secp256k1 from '@noble/secp256k1'
|
|
3
|
+
|
|
4
|
+
export function encrypt(privkey, pubkey, text) {
|
|
5
|
+
const key = secp256k1.getSharedSecret(privkey, '02' + pubkey)
|
|
6
|
+
const normalizedKey = getOnlyXFromFullSharedSecret(key)
|
|
7
|
+
|
|
8
|
+
let iv = crypto.randomFillSync(new Uint8Array(16))
|
|
9
|
+
var cipher = crypto.createCipheriv(
|
|
10
|
+
'aes-256-cbc',
|
|
11
|
+
Buffer.from(normalizedKey, 'hex'),
|
|
12
|
+
iv
|
|
13
|
+
)
|
|
14
|
+
let encryptedMessage = cipher.update(text, 'utf8', 'base64')
|
|
15
|
+
encryptedMessage += cipher.final('base64')
|
|
16
|
+
|
|
17
|
+
return [encryptedMessage, Buffer.from(iv.buffer).toString('base64')]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function decrypt(privkey, pubkey, ciphertext, iv) {
|
|
21
|
+
const key = secp256k1.getSharedSecret(privkey, '02' + pubkey)
|
|
22
|
+
const normalizedKey = getOnlyXFromFullSharedSecret(key)
|
|
23
|
+
|
|
24
|
+
var decipher = crypto.createDecipheriv(
|
|
25
|
+
'aes-256-cbc',
|
|
26
|
+
Buffer.from(normalizedKey, 'hex'),
|
|
27
|
+
Buffer.from(iv, 'base64')
|
|
28
|
+
)
|
|
29
|
+
let decryptedMessage = decipher.update(ciphertext, 'base64')
|
|
30
|
+
decryptedMessage += decipher.final('utf8')
|
|
31
|
+
|
|
32
|
+
return decryptedMessage
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getOnlyXFromFullSharedSecret(fullSharedSecretCoordinates) {
|
|
36
|
+
return fullSharedSecretCoordinates.substr(2, 64)
|
|
37
|
+
}
|
package/nip05.js
CHANGED
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nostr-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Tools for making a Nostr client.",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "dist/nostr-tools.esm.min.js",
|
|
6
|
+
"module": "dist/nostr-tools.esm.min.js",
|
|
7
|
+
"browser": "dist/nostr-tools.umd.min.js",
|
|
6
8
|
"repository": {
|
|
7
9
|
"type": "git",
|
|
8
10
|
"url": "https://github.com/fiatjaf/nostr-tools.git"
|
|
9
11
|
},
|
|
10
12
|
"dependencies": {
|
|
13
|
+
"@noble/secp256k1": "^1.3.0",
|
|
11
14
|
"buffer": "^6.0.3",
|
|
12
15
|
"dns-packet": "^5.2.4",
|
|
13
|
-
"noble-secp256k1": "^1.1.1",
|
|
14
16
|
"websocket-polyfill": "^0.0.3"
|
|
15
17
|
},
|
|
16
18
|
"keywords": [
|
|
@@ -24,5 +26,11 @@
|
|
|
24
26
|
"censorship",
|
|
25
27
|
"censorship-resistance",
|
|
26
28
|
"client"
|
|
27
|
-
]
|
|
29
|
+
],
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"rollup": "^2.61.1"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"prepublish": "rollup -c"
|
|
35
|
+
}
|
|
28
36
|
}
|
package/rollup.config.js
ADDED
package/utils.js
CHANGED