corestore 5.8.0 → 6.0.0-alpha.1

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/index.js CHANGED
@@ -1,432 +1,223 @@
1
- const HypercoreProtocol = require('hypercore-protocol')
2
- const Nanoresource = require('nanoresource/emitter')
3
- const hypercore = require('hypercore')
4
- const hypercoreCrypto = require('hypercore-crypto')
5
- const datEncoding = require('dat-encoding')
6
- const maybe = require('call-me-maybe')
1
+ const { EventEmitter } = require('events')
2
+ const crypto = require('hypercore-crypto')
3
+ const sodium = require('sodium-universal')
4
+ const Hypercore = require('hypercore')
7
5
 
8
- const RefPool = require('refpool')
9
- const deriveSeed = require('derive-key')
10
- const derivedStorage = require('derived-key-storage')
11
- const raf = require('random-access-file')
6
+ const KeyManager = require('./lib/keys')
12
7
 
13
- const MASTER_KEY_FILENAME = 'master_key'
14
- const NAMESPACE = 'corestore'
15
- const NAMESPACE_SEPERATOR = ':'
8
+ const CORES_DIR = 'cores'
9
+ const KEYS_DIR = 'keys'
10
+ const USERDATA_NAME_KEY = '@corestore/name'
11
+ const USERDATA_NAMESPACE_KEY = '@corestore/namespace'
12
+ const DEFAULT_NAMESPACE = generateNamespace('@corestore/default')
16
13
 
17
- class InnerCorestore extends Nanoresource {
14
+ module.exports = class Corestore extends EventEmitter {
18
15
  constructor (storage, opts = {}) {
19
16
  super()
20
17
 
21
- if (typeof storage === 'string') storage = defaultStorage(storage)
22
- if (typeof storage !== 'function') throw new Error('Storage should be a function or string')
23
- this.storage = storage
18
+ this.storage = Hypercore.defaultStorage(storage, { lock: KEYS_DIR + '/profile' })
24
19
 
25
- this.opts = opts
20
+ this.cores = opts._cores || new Map()
21
+ this.keys = opts.keys
26
22
 
27
- this._replicationStreams = []
28
- this.cache = new RefPool({
29
- maxSize: opts.cacheSize || 1000,
30
- close: core => {
31
- core.close(err => {
32
- if (err) this.emit('error', err)
33
- })
34
- }
35
- })
36
-
37
- // Generated in _open
38
- this._masterKey = opts.masterKey || null
39
- this._id = hypercoreCrypto.randomBytes(8)
40
- }
41
-
42
- // Nanoresource Methods
43
-
44
- _open (cb) {
45
- if (this._masterKey) return cb()
46
- const keyStorage = this.storage(MASTER_KEY_FILENAME)
47
- keyStorage.read(0, 32, (err, key) => {
48
- if (err) {
49
- this._masterKey = hypercoreCrypto.randomBytes(32)
50
- return keyStorage.write(0, this._masterKey, err => {
51
- if (err) return cb(err)
52
- keyStorage.close(cb)
53
- })
54
- }
55
- this._masterKey = key
56
- keyStorage.close(cb)
57
- })
58
- }
59
-
60
- _close (cb) {
61
- let error = null
62
- for (const { stream } of this._replicationStreams) {
63
- stream.destroy()
64
- }
65
- if (!this.cache.size) return process.nextTick(cb, null)
66
- let remaining = this.cache.size
67
- for (const { value: core } of this.cache.entries.values()) {
68
- core.close(err => {
69
- if (err) error = err
70
- if (!--remaining) {
71
- if (error) return cb(error)
72
- return cb(null)
73
- }
74
- })
75
- }
76
- }
77
-
78
- // Private Methods
79
-
80
- _checkIfExists (dkey, cb) {
81
- dkey = encodeKey(dkey)
82
- if (this.cache.has(dkey)) return process.nextTick(cb, null, true)
23
+ this._namespace = opts._namespace || DEFAULT_NAMESPACE
24
+ this._replicationStreams = opts._streams || []
83
25
 
84
- const coreStorage = this.storage([dkey.slice(0, 2), dkey.slice(2, 4), dkey, 'key'].join('/'))
85
-
86
- coreStorage.read(0, 32, (err, key) => {
87
- if (err) return cb(err)
88
- coreStorage.close(err => {
89
- if (err) return cb(err)
90
- if (!key) return cb(null, false)
91
- return cb(null, true)
92
- })
93
- })
26
+ this._opening = opts._opening ? opts._opening.then(() => this._open()) : this._open()
27
+ this._opening.catch(noop)
28
+ this.ready = () => this._opening
94
29
  }
95
30
 
96
- _injectIntoReplicationStreams (core) {
97
- for (const { stream, opts } of this._replicationStreams) {
98
- this._replicateCore(false, core, stream, { ...opts })
31
+ async _open () {
32
+ if (this.keys) {
33
+ this.keys = await this.keys // opts.keys can be a Promise that resolves to a KeyManager
34
+ } else {
35
+ this.keys = await KeyManager.fromStorage(p => this.storage(KEYS_DIR + '/' + p))
99
36
  }
100
37
  }
101
38
 
102
- _replicateCore (isInitiator, core, mainStream, opts) {
103
- if (!core) return
104
- core.ready(function (err) {
105
- if (err) return
106
- core.replicate(isInitiator, {
107
- ...opts,
108
- stream: mainStream
109
- })
110
- })
111
- }
112
-
113
- _deriveSecret (namespace, name) {
114
- return deriveSeed(namespace, this._masterKey, name)
115
- }
116
-
117
- _generateKeyPair (name) {
118
- if (typeof name === 'string') name = Buffer.from(name)
119
- else if (!name) name = hypercoreCrypto.randomBytes(32)
120
-
121
- const seed = this._deriveSecret(NAMESPACE, name)
122
-
123
- const keyPair = hypercoreCrypto.keyPair(seed)
124
- const discoveryKey = hypercoreCrypto.discoveryKey(keyPair.publicKey)
125
- return { name, publicKey: keyPair.publicKey, secretKey: keyPair.secretKey, discoveryKey }
126
- }
127
-
128
- _generateKeys (coreOpts) {
129
- if (!coreOpts) coreOpts = {}
130
- if (typeof coreOpts === 'string') coreOpts = Buffer.from(coreOpts, 'hex')
131
- if (Buffer.isBuffer(coreOpts)) coreOpts = { key: coreOpts }
132
-
133
- if (coreOpts.keyPair) {
134
- const publicKey = coreOpts.keyPair.publicKey
135
- const secretKey = coreOpts.keyPair.secretKey
39
+ async _generateKeys (opts) {
40
+ if (opts.discoveryKey) {
136
41
  return {
137
- publicKey,
138
- secretKey,
139
- discoveryKey: hypercoreCrypto.discoveryKey(publicKey),
140
- name: null
42
+ keyPair: null,
43
+ sign: null,
44
+ discoveryKey: opts.discoveryKey
141
45
  }
142
46
  }
143
- if (coreOpts.key) {
144
- const publicKey = decodeKey(coreOpts.key)
47
+ if (!opts.name) {
145
48
  return {
146
- publicKey,
147
- secretKey: null,
148
- discoveryKey: hypercoreCrypto.discoveryKey(publicKey),
149
- name: null
49
+ keyPair: {
50
+ publicKey: opts.publicKey,
51
+ secretKey: opts.secretKey
52
+ },
53
+ sign: opts.sign,
54
+ discoveryKey: crypto.discoveryKey(opts.publicKey)
150
55
  }
151
56
  }
152
- if (coreOpts.default || coreOpts.name) {
153
- if (!coreOpts.name) throw new Error('If the default option is set, a name must be specified.')
154
- return this._generateKeyPair(coreOpts.name)
155
- }
156
- if (coreOpts.discoveryKey) {
157
- const discoveryKey = decodeKey(coreOpts.discoveryKey)
158
- return {
159
- publicKey: null,
160
- secretKey: null,
161
- discoveryKey,
162
- name: null
163
- }
57
+ const { publicKey, sign } = await this.keys.createHypercoreKeyPair(opts.name, this._namespace)
58
+ return {
59
+ keyPair: {
60
+ publicKey,
61
+ secretKey: null
62
+ },
63
+ sign,
64
+ discoveryKey: crypto.discoveryKey(publicKey)
164
65
  }
165
- return this._generateKeyPair(null)
166
66
  }
167
67
 
168
- // Public Methods
68
+ async _postload (core) {
69
+ const name = await core.getUserData(USERDATA_NAME_KEY)
70
+ if (!name) return
169
71
 
170
- isLoaded (coreOpts) {
171
- const generatedKeys = this._generateKeys(coreOpts)
172
- return this.cache.has(encodeKey(generatedKeys.discoveryKey))
173
- }
72
+ const namespace = await core.getUserData(USERDATA_NAMESPACE_KEY)
73
+ const { publicKey, sign } = await this.keys.createHypercoreKeyPair(name.toString(), namespace)
74
+ if (!publicKey.equals(core.key)) throw new Error('Stored core key does not match the provided name')
174
75
 
175
- isExternal (coreOpts) {
176
- const generatedKeys = this._generateKeys(coreOpts)
177
- const entry = this._cache.entry(encodeKey(generatedKeys.discoveryKey))
178
- if (!entry) return false
179
- return entry.refs !== 0
76
+ // TODO: Should Hypercore expose a helper for this, or should postload return keypair/sign?
77
+ core.sign = sign
78
+ core.key = publicKey
79
+ core.writable = true
180
80
  }
181
81
 
182
- get (coreOpts = {}) {
183
- if (!this.opened) throw new Error('Corestore.ready must be called before get.')
184
-
185
- const self = this
82
+ async _preload (opts) {
83
+ await this.ready()
186
84
 
187
- const generatedKeys = this._generateKeys(coreOpts)
188
- const { publicKey, discoveryKey, secretKey } = generatedKeys
189
- const id = encodeKey(discoveryKey)
85
+ const { discoveryKey, keyPair, sign } = await this._generateKeys(opts)
86
+ const id = discoveryKey.toString('hex')
190
87
 
191
- const cached = this.cache.get(id)
192
- if (cached) return cached
193
-
194
- const storageRoot = [id.slice(0, 2), id.slice(2, 4), id].join('/')
195
-
196
- const keyStorage = derivedStorage(createStorage, (name, cb) => {
197
- if (name) {
198
- const res = this._generateKeyPair(name)
199
- if (discoveryKey && (!discoveryKey.equals((res.discoveryKey)))) {
200
- return cb(new Error('Stored an incorrect name.'))
201
- }
202
- return cb(null, res)
88
+ while (this.cores.has(id)) {
89
+ const existing = this.cores.get(id)
90
+ if (existing) {
91
+ if (!existing.closing) return { from: existing, keyPair, sign }
92
+ await existing.close()
203
93
  }
204
- if (secretKey) return cb(null, generatedKeys)
205
- if (publicKey) return cb(null, { name: null, publicKey, secretKey: null })
206
- const err = new Error('Unknown key pair.')
207
- err.unknownKeyPair = true
208
- return cb(err)
209
- })
210
-
211
- const cacheOpts = { ...this.opts.cache }
212
- if (coreOpts.cache) {
213
- if (coreOpts.cache.data === false) delete cacheOpts.data
214
- if (coreOpts.cache.tree === false) delete cacheOpts.tree
215
94
  }
216
- if (cacheOpts.data) cacheOpts.data = cacheOpts.data.namespace()
217
- if (cacheOpts.tree) cacheOpts.tree = cacheOpts.tree.namespace()
218
-
219
- const core = hypercore(name => {
220
- if (name === 'key') return keyStorage.key
221
- if (name === 'secret_key') return keyStorage.secretKey
222
- return createStorage(name)
223
- }, publicKey, {
224
- ...this.opts,
225
- ...coreOpts,
226
- cache: cacheOpts,
227
- createIfMissing: !!publicKey
228
- })
229
-
230
- this.cache.set(id, core)
231
- core.ifAvailable.wait()
232
95
 
233
- var errored = false
234
- core.once('error', onerror)
235
- core.once('ready', onready)
236
- core.once('close', onclose)
237
-
238
- return core
239
-
240
- function onready () {
241
- if (errored) return
242
- self.emit('feed', core, coreOpts)
243
- core.removeListener('error', onerror)
244
- self._injectIntoReplicationStreams(core)
245
- // TODO: nexttick here needed? prob not, just legacy
246
- process.nextTick(() => core.ifAvailable.continue())
96
+ const userData = {}
97
+ if (opts.name) {
98
+ userData[USERDATA_NAME_KEY] = Buffer.from(opts.name)
99
+ userData[USERDATA_NAMESPACE_KEY] = this._namespace
247
100
  }
248
101
 
249
- function onerror (err) {
250
- errored = true
251
- core.ifAvailable.continue()
252
- self.cache.delete(id)
253
- if (err.unknownKeyPair) {
254
- // If an error occurs during creation by discovery key, then that core does not exist on disk.
255
- // TODO: This should not throw, but should propagate somehow.
256
- }
257
- }
102
+ // No more async ticks allowed after this point -- necessary for caching
258
103
 
259
- function onclose () {
260
- self.cache.delete(id)
261
- }
262
-
263
- function createStorage (name) {
264
- return self.storage(storageRoot + '/' + name)
265
- }
266
- }
267
-
268
- replicate (isInitiator, cores, replicationOpts = {}) {
269
- const self = this
270
-
271
- const finalOpts = { ...this.opts, ...replicationOpts }
272
- const mainStream = replicationOpts.stream || new HypercoreProtocol(isInitiator, { ...finalOpts })
273
- var closed = false
274
-
275
- for (const core of cores) {
276
- this._replicateCore(isInitiator, core, mainStream, { ...finalOpts })
277
- }
278
-
279
- mainStream.on('discovery-key', ondiscoverykey)
280
- mainStream.on('finish', onclose)
281
- mainStream.on('end', onclose)
282
- mainStream.on('close', onclose)
283
-
284
- const streamState = { stream: mainStream, opts: finalOpts }
285
- this._replicationStreams.push(streamState)
286
-
287
- return mainStream
288
-
289
- function ondiscoverykey (dkey) {
290
- // Get will automatically add the core to all replication streams.
291
- self._checkIfExists(dkey, (err, exists) => {
292
- if (closed) return
293
- if (err || !exists) return mainStream.close(dkey)
294
- const passiveCore = self.get({ discoveryKey: dkey })
295
- self._replicateCore(false, passiveCore, mainStream, { ...finalOpts })
296
- })
297
- }
298
-
299
- function onclose () {
300
- if (!closed) {
301
- self._replicationStreams.splice(self._replicationStreams.indexOf(streamState), 1)
302
- closed = true
303
- }
304
- }
305
- }
306
- }
307
-
308
- class Corestore extends Nanoresource {
309
- constructor (storage, opts = {}) {
310
- super()
311
-
312
- this.storage = storage
313
- this.name = opts.name || 'default'
314
- this.inner = opts.inner || new InnerCorestore(storage, opts)
315
- this.cache = this.inner.cache
316
- this.store = this // Backwards-compat for NamespacedCorestore
317
-
318
- this._parent = opts.parent
319
- this._isNamespaced = !!opts.name
320
- this._openedCores = new Map()
321
-
322
- const onfeed = feed => this.emit('feed', feed)
323
- const onerror = err => this.emit('error', err)
324
- this.inner.on('feed', onfeed)
325
- this.inner.on('error', onerror)
326
- this._unlisten = () => {
327
- this.inner.removeListener('feed', onfeed)
328
- this.inner.removeListener('error', onerror)
329
- }
330
- }
331
-
332
- ready (cb) {
333
- return maybe(cb, new Promise((resolve, reject) => {
334
- this.open(err => {
335
- if (err) return reject(err)
336
- return resolve()
337
- })
338
- }))
339
- }
340
-
341
- // Nanoresource Methods
342
-
343
- _open (cb) {
344
- return this.inner.open(cb)
345
- }
104
+ const storageRoot = [CORES_DIR, id.slice(0, 2), id.slice(2, 4), id].join('/')
105
+ const core = new Hypercore(p => this.storage(storageRoot + '/' + p), {
106
+ autoClose: true,
107
+ keyPair: {
108
+ publicKey: keyPair.publicKey,
109
+ secretKey: null
110
+ },
111
+ userData,
112
+ sign: null,
113
+ postload: this._postload.bind(this),
114
+ createIfMissing: !!opts.keyPair
115
+ })
346
116
 
347
- _close (cb) {
348
- this._unlisten()
349
- if (!this._parent) return this.inner.close(cb)
350
- for (const dkey of this._openedCores) {
351
- this.cache.decrement(dkey)
117
+ this.cores.set(id, core)
118
+ for (const stream of this._replicationStreams) {
119
+ core.replicate(stream)
352
120
  }
353
- return process.nextTick(cb, null)
354
- }
355
-
356
- // Private Methods
121
+ core.once('close', () => {
122
+ this.cores.delete(id)
123
+ })
357
124
 
358
- _maybeIncrement (core) {
359
- const id = encodeKey(core.discoveryKey)
360
- if (this._openedCores.has(id)) return
361
- this._openedCores.set(id, core)
362
- this.cache.increment(id)
125
+ return { from: core, keyPair, sign }
363
126
  }
364
127
 
365
- // Public Methods
366
-
367
- get (coreOpts = {}) {
368
- if (Buffer.isBuffer(coreOpts)) coreOpts = { key: coreOpts }
369
- const core = this.inner.get(coreOpts)
370
- this._maybeIncrement(core)
128
+ get (opts = {}) {
129
+ opts = validateGetOptions(opts)
130
+ const core = new Hypercore(null, {
131
+ ...opts,
132
+ name: null,
133
+ preload: () => this._preload(opts)
134
+ })
371
135
  return core
372
136
  }
373
137
 
374
- default (coreOpts = {}) {
375
- if (Buffer.isBuffer(coreOpts)) coreOpts = { key: coreOpts }
376
- return this.get({ ...coreOpts, name: this.name })
138
+ replicate (opts = {}) {
139
+ const stream = isStream(opts) ? opts : (opts.stream || Hypercore.createProtocolStream(opts))
140
+ for (const core of this.cores.values()) {
141
+ core.replicate(stream)
142
+ }
143
+ stream.on('discovery-key', discoveryKey => {
144
+ const core = this.get({ discoveryKey })
145
+ core.ready().then(() => {
146
+ core.replicate(stream)
147
+ }, () => {
148
+ stream.close(discoveryKey)
149
+ })
150
+ })
151
+ this._replicationStreams.push(stream)
152
+ stream.once('close', () => {
153
+ this._replicationStreams.splice(this._replicationStreams.indexOf(stream), 1)
154
+ })
155
+ return stream
377
156
  }
378
157
 
379
158
  namespace (name) {
380
- if (!name) name = hypercoreCrypto.randomBytes(32)
381
- if (Buffer.isBuffer(name)) name = name.toString('hex')
382
- name = this._isNamespaced ? this.name + NAMESPACE_SEPERATOR + name : name
159
+ if (!Buffer.isBuffer(name)) name = Buffer.from(name)
383
160
  return new Corestore(this.storage, {
384
- inner: this.inner,
385
- parent: this,
386
- name
161
+ _namespace: generateNamespace(this._namespace, name),
162
+ _opening: this._opening,
163
+ _cores: this.cores,
164
+ _streams: this._replicationStreams,
165
+ keys: this._opening.then(() => this.keys)
387
166
  })
388
167
  }
389
168
 
390
- replicate (isInitiator, opts) {
391
- const cores = !this._parent ? allReferenced(this.cache) : this._openedCores.values()
392
- return this.inner.replicate(isInitiator, cores, opts)
393
- }
394
-
395
- isLoaded (coreOpts) {
396
- return this.inner.isLoaded(coreOpts)
397
- }
398
-
399
- isExternal (coreOpts) {
400
- return this.inner.isExternal(coreOpts)
169
+ async _close () {
170
+ if (this._closing) return this._closing
171
+ const closePromises = []
172
+ for (const core of this.cores.values()) {
173
+ closePromises.push(core.close())
174
+ }
175
+ await Promise.allSettled(closePromises)
176
+ for (const stream of this._replicationStreams) {
177
+ stream.destroy()
178
+ }
401
179
  }
402
180
 
403
- list () {
404
- return new Map([...this._openedCores])
181
+ close () {
182
+ if (this._closing) return this._closing
183
+ this._closing = this._close()
184
+ this._closing.catch(noop)
185
+ return this._closing
405
186
  }
406
- }
407
187
 
408
- function * allReferenced (cache) {
409
- for (const entry of cache.entries.values()) {
410
- if (entry.refs > 0) yield entry.value
411
- continue
188
+ static createToken () {
189
+ return KeyManager.createToken()
412
190
  }
413
191
  }
414
192
 
415
- function encodeKey (key) {
416
- return Buffer.isBuffer(key) ? datEncoding.encode(key) : key
193
+ function validateGetOptions (opts) {
194
+ if (Buffer.isBuffer(opts)) return { key: opts, publicKey: opts }
195
+ if (opts.key) {
196
+ opts.publicKey = opts.key
197
+ }
198
+ if (opts.keyPair) {
199
+ opts.publicKey = opts.keyPair.publicKey
200
+ opts.secretKey = opts.keyPair.secretKey
201
+ }
202
+ if (opts.name && typeof opts.name !== 'string') throw new Error('name option must be a String')
203
+ if (opts.name && opts.secretKey) throw new Error('Cannot provide both a name and a secret key')
204
+ if (opts.publicKey && !Buffer.isBuffer(opts.publicKey)) throw new Error('publicKey option must be a Buffer')
205
+ if (opts.secretKey && !Buffer.isBuffer(opts.secretKey)) throw new Error('secretKey option must be a Buffer')
206
+ if (opts.discoveryKey && !Buffer.isBuffer(opts.discoveryKey)) throw new Error('discoveryKey option must be a Buffer')
207
+ if (!opts.name && !opts.publicKey) throw new Error('Must provide either a name or a publicKey')
208
+ return opts
417
209
  }
418
210
 
419
- function decodeKey (key) {
420
- return (typeof key === 'string') ? datEncoding.decode(key) : key
211
+ function generateNamespace (first, second) {
212
+ if (!Buffer.isBuffer(first)) first = Buffer.from(first)
213
+ if (second && !Buffer.isBuffer(second)) second = Buffer.from(second)
214
+ const out = Buffer.allocUnsafe(32)
215
+ sodium.crypto_generichash(out, second ? Buffer.concat([first, second]) : first)
216
+ return out
421
217
  }
422
218
 
423
- function defaultStorage (dir) {
424
- return function (name) {
425
- try {
426
- var lock = name.endsWith('/bitfield') ? require('fd-lock') : null
427
- } catch (err) {}
428
- return raf(name, { directory: dir, lock: lock })
429
- }
219
+ function isStream (s) {
220
+ return typeof s === 'object' && s && typeof s.pipe === 'function'
430
221
  }
431
222
 
432
- module.exports = Corestore
223
+ function noop () {}