corestore 6.0.0-beta2 → 6.0.1-alpha.12

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.
@@ -1,5 +1,4 @@
1
- name: Test on Node.js
2
-
1
+ name: Build Status
3
2
  on:
4
3
  push:
5
4
  branches:
@@ -11,13 +10,13 @@ jobs:
11
10
  build:
12
11
  strategy:
13
12
  matrix:
14
- node-version: [12.x, 14.x]
15
- os: [ubuntu-16.04, macos-latest, windows-latest]
13
+ node-version: [lts/*]
14
+ os: [ubuntu-latest, macos-latest, windows-latest]
16
15
  runs-on: ${{ matrix.os }}
17
16
  steps:
18
17
  - uses: actions/checkout@v2
19
18
  - name: Use Node.js ${{ matrix.node-version }}
20
- uses: actions/setup-node@v1
19
+ uses: actions/setup-node@v2
21
20
  with:
22
21
  node-version: ${{ matrix.node-version }}
23
22
  - run: npm install
package/README.md CHANGED
@@ -1,2 +1,59 @@
1
- # neocorestore
2
- A new take on Corestore
1
+ # Corestore v6
2
+
3
+ Corestore is a Hypercore factory that makes it easier to manage large collections of named Hypercores.
4
+
5
+ Corestore provides:
6
+ 1. __Key Derivation__ - All writable Hypercore keys are derived from a single master key and a user-provided name.
7
+ 2. __Session Handling__ - If a single Hypercore is loaded multiple times through the `get` method, the underlying resources will only be opened once (using Hypercore 10's new session feature). Once all sessions are closed, the resources will be released.
8
+ 3. __Storage Management__ - Hypercores can be stored in any random-access-storage instance, where they will be keyed by their discovery keys.
9
+ 4. __Namespacing__ - You can share a single Corestore instance between multiple applications or components without worrying about naming collisions by creating "namespaces" (e.g. `corestore.namespace('my-app').get({ name: 'main' })
10
+
11
+ ### Installation
12
+ `npm install corestore@next`
13
+
14
+ ### Usage
15
+ A corestore instance can be constructed with a random-access-storage module, a function that returns a random-access-storage module given a path, or a string. If a string is specified, it will be assumed to be a path to a local storage directory:
16
+ ```js
17
+ const Corestore = require('corestore')
18
+
19
+ const store = new Corestore('./my-storage')
20
+ const core1 = store.get({ name: 'core-1' })
21
+ const core2 = store.get({ name: 'core-2' })
22
+ ```
23
+
24
+ ### API
25
+ #### `const store = new Corestore(storage)`
26
+ Create a new Corestore instance.
27
+
28
+ `storage` can be either a random-access-storage module, a string, or a function that takes a path and returns an random-access-storage instance.
29
+
30
+ #### `const core = store.get(key | { name: 'a-name', ...hypercoreOpts})`
31
+ Loads a Hypercore, either by name (if the `name` option is provided), or from the provided key (if the first argument is a Buffer, or if the `key` options is set).
32
+
33
+ If that Hypercore has previously been loaded, subsequent calls to `get` will return a new Hypercore session on the existing core.
34
+
35
+ All other options besides `name` and `key` will be forwarded to the Hypercore constructor.
36
+
37
+ #### `const stream = store.replicate(opts)`
38
+ Creates a replication stream that's capable of replicating all Hypercores that are managed by the Corestore, assuming the remote peer has the correct capabilities.
39
+
40
+ `opts` will be forwarded to Hypercore's `replicate` function.
41
+
42
+ Corestore replicates in an "all-to-all" fashion, meaning that when replication begins, it will attempt to replicate every Hypercore that's currently loaded and in memory. These attempts will fail if the remote side doesn't have a Hypercore's capability -- Corestore replication does not exchange Hypercore keys.
43
+
44
+ If the remote side dynamically adds a new Hypercore to the replication stream, Corestore will load and replicatethat core if possible.
45
+
46
+ #### `const store = store.namespace(name)`
47
+ Create a new namespaced Corestore. Namespacing is useful if you're going to be sharing a single Corestore instance between many applications or components, as it prevents name collisions.
48
+
49
+ Namespaces can be chained:
50
+ ```js
51
+ const ns1 = store.namespace('a')
52
+ const ns2 = ns1.namespace('b')
53
+ const core1 = ns1.get({ name: 'main' }) // These will load different Hypercores
54
+ const core2 = ns2.get({ name: 'main' })
55
+ ```
56
+
57
+ ### License
58
+ MIT
59
+
package/index.js CHANGED
@@ -1,122 +1,257 @@
1
- const { NanoresourcePromise: Nanoresource } = require('nanoresource-promise/emitter')
2
- const raf = require('random-access-file')
1
+ const { EventEmitter } = require('events')
2
+ const safetyCatch = require('safety-catch')
3
+ const crypto = require('hypercore-crypto')
4
+ const sodium = require('sodium-universal')
5
+ const Hypercore = require('hypercore')
6
+ const b4a = require('b4a')
3
7
 
4
- const Replicator = require('./lib/replicator')
5
- const Index = require('./lib/db')
6
- const Loader = require('./lib/loader')
7
- const errors = require('./lib/errors')
8
+ const KeyManager = require('./lib/keys')
8
9
 
9
- module.exports = class Corestore extends Nanoresource {
10
+ const CORES_DIR = 'cores'
11
+ const PROFILES_DIR = 'profiles'
12
+ const USERDATA_NAME_KEY = '@corestore/name'
13
+ const USERDATA_NAMESPACE_KEY = '@corestore/namespace'
14
+ const DEFAULT_NAMESPACE = generateNamespace('@corestore/default')
15
+
16
+ module.exports = class Corestore extends EventEmitter {
10
17
  constructor (storage, opts = {}) {
11
18
  super()
12
19
 
13
- if (typeof storage === 'string') storage = defaultStorage(storage)
14
- if (typeof storage !== 'function') throw new errors.InvalidStorageError()
15
- this.storage = storage
20
+ this.storage = Hypercore.defaultStorage(storage, { lock: PROFILES_DIR + '/default' })
16
21
 
17
- this._namespace = opts.namespace || []
18
- this._db = opts._db || new Index(this.storage, opts)
19
- this._loader = opts._loader || new Loader(this.storage, this._db, opts)
20
- this._replicator = opts._replicator || new Replicator(this._loader, opts)
22
+ this.cores = opts._cores || new Map()
23
+ this.keys = opts.keys
21
24
 
22
- this._db.on('error', err => this.emit('db-error', err))
23
- this._loader.on('error', err => this.emit('error', err))
24
- this._loader.on('core', (core, opts) => this.emit('core', core, opts))
25
- this._loader.on('core', (core, opts) => this.emit('feed', core, opts))
25
+ this._namespace = opts._namespace || DEFAULT_NAMESPACE
26
+ this._replicationStreams = opts._streams || []
27
+ this._streamSessions = opts._streamSessions || new Map()
26
28
 
27
- this.ready = this.open.bind(this)
29
+ this._opening = opts._opening ? opts._opening.then(() => this._open()) : this._open()
30
+ this._opening.catch(safetyCatch)
31
+ this.ready = () => this._opening
28
32
  }
29
33
 
30
- get cache () {
31
- return this._loader.cache
34
+ async _open () {
35
+ if (this.keys) {
36
+ this.keys = await this.keys // opts.keys can be a Promise that resolves to a KeyManager
37
+ } else {
38
+ this.keys = await KeyManager.fromStorage(p => this.storage(PROFILES_DIR + '/' + p))
39
+ }
32
40
  }
33
41
 
34
- // Nanoresource Methods
42
+ async _generateKeys (opts) {
43
+ if (opts._discoveryKey) {
44
+ return {
45
+ keyPair: null,
46
+ auth: null,
47
+ discoveryKey: opts._discoveryKey
48
+ }
49
+ }
50
+ if (!opts.name) {
51
+ return {
52
+ keyPair: {
53
+ publicKey: opts.publicKey,
54
+ secretKey: opts.secretKey
55
+ },
56
+ sign: opts.sign,
57
+ auth: opts.auth,
58
+ discoveryKey: crypto.discoveryKey(opts.publicKey)
59
+ }
60
+ }
61
+ const { publicKey, auth } = await this.keys.createHypercoreKeyPair(opts.name, this._namespace)
62
+ return {
63
+ keyPair: {
64
+ publicKey,
65
+ secretKey: null
66
+ },
67
+ auth,
68
+ discoveryKey: crypto.discoveryKey(publicKey)
69
+ }
70
+ }
35
71
 
36
- async _open () {
37
- await this._db.open()
38
- return this._loader.open()
72
+ _getPrereadyUserData (core, key) {
73
+ for (const { key: savedKey, value } of core.core.header.userData) {
74
+ if (key === savedKey) return value
75
+ }
76
+ return null
39
77
  }
40
78
 
41
- async _close () {
42
- await this._replicator.close()
43
- await this._loader.close()
44
- return this._db.close()
79
+ async _preready (core) {
80
+ const name = this._getPrereadyUserData(core, USERDATA_NAME_KEY)
81
+ if (!name) return
82
+
83
+ const namespace = this._getPrereadyUserData(core, USERDATA_NAMESPACE_KEY)
84
+ const { publicKey, auth } = await this.keys.createHypercoreKeyPair(b4a.toString(name), namespace)
85
+ if (!b4a.equals(publicKey, core.key)) throw new Error('Stored core key does not match the provided name')
86
+
87
+ // TODO: Should Hypercore expose a helper for this, or should preready return keypair/auth?
88
+ core.auth = auth
89
+ core.key = publicKey
90
+ core.writable = true
45
91
  }
46
92
 
47
- // Private Methods
93
+ async _preload (opts) {
94
+ await this.ready()
95
+
96
+ const { discoveryKey, keyPair, auth } = await this._generateKeys(opts)
97
+ const id = b4a.toString(discoveryKey, 'hex')
48
98
 
49
- _validateGetOptions (opts) {
50
- if (typeof opts === 'object') {
51
- if (!Buffer.isBuffer(opts)) {
52
- if (!opts.key && !opts.name && !opts.keyPair) throw new errors.InvalidOptionsError()
53
- } else {
54
- opts = { key: opts }
99
+ while (this.cores.has(id)) {
100
+ const existing = this.cores.get(id)
101
+ if (existing.opened && !existing.closing) return { from: existing, keyPair, auth }
102
+ if (!existing.opened) {
103
+ await existing.ready().catch(safetyCatch)
104
+ } else if (existing.closing) {
105
+ await existing.close()
55
106
  }
56
- } else {
57
- opts = { key: Buffer.from(opts, 'hex') }
58
107
  }
59
- if (opts.key && typeof opts.key === 'string') opts.key = Buffer.from(opts.key, 'hex')
60
- if (opts.key && opts.key.length !== 32) throw new errors.InvalidKeyError()
61
- if (opts.name && !Array.isArray(opts.name)) opts.name = [opts.name]
62
- return opts
63
- }
64
108
 
65
- // Public Methods
109
+ const userData = {}
110
+ if (opts.name) {
111
+ userData[USERDATA_NAME_KEY] = b4a.from(opts.name)
112
+ userData[USERDATA_NAMESPACE_KEY] = this._namespace
113
+ }
66
114
 
67
- get (opts = {}) {
68
- opts = this._validateGetOptions(opts)
69
- if (!this.opened) this.open().catch(err => this.emit('error', err))
70
- return this._loader.get(this._namespace, opts)
115
+ // No more async ticks allowed after this point -- necessary for caching
116
+
117
+ const storageRoot = [CORES_DIR, id.slice(0, 2), id.slice(2, 4), id].join('/')
118
+ const core = new Hypercore(p => this.storage(storageRoot + '/' + p), {
119
+ _preready: this._preready.bind(this),
120
+ autoClose: true,
121
+ encryptionKey: opts.encryptionKey || null,
122
+ userData,
123
+ auth,
124
+ createIfMissing: !opts._discoveryKey,
125
+ keyPair: keyPair && keyPair.publicKey
126
+ ? {
127
+ publicKey: keyPair.publicKey,
128
+ secretKey: null
129
+ }
130
+ : null
131
+ })
132
+
133
+ this.cores.set(id, core)
134
+ core.ready().then(() => {
135
+ for (const { stream } of this._replicationStreams) {
136
+ const sessions = this._streamSessions.get(stream)
137
+ const session = core.session()
138
+ sessions.push(session)
139
+ core.replicate(stream)
140
+ }
141
+ }, () => {
142
+ this.cores.delete(id)
143
+ })
144
+ core.once('close', () => {
145
+ this.cores.delete(id)
146
+ })
147
+
148
+ return { from: core, keyPair, auth }
71
149
  }
72
150
 
73
- namespace (name) {
74
- if (!name) throw new Error('A name must be provided as the first argument.')
75
- if (Buffer.isBuffer(name)) name = name.toString('hex')
76
- return new Corestore(this.storage, {
77
- namespace: [...this._namespace, name],
78
- _db: this._db,
79
- _loader: this._loader,
80
- _replicator: this._replicator
151
+ get (opts = {}) {
152
+ opts = validateGetOptions(opts)
153
+ const core = new Hypercore(null, {
154
+ ...opts,
155
+ name: null,
156
+ preload: () => this._preload(opts)
81
157
  })
158
+ return core
82
159
  }
83
160
 
84
161
  replicate (isInitiator, opts) {
85
- return this._replicator.replicate(isInitiator, opts)
162
+ const isExternal = isStream(isInitiator) || !!(opts && opts.stream)
163
+ const stream = Hypercore.createProtocolStream(isInitiator, {
164
+ ...opts,
165
+ ondiscoverykey: discoveryKey => {
166
+ const core = this.get({ _discoveryKey: discoveryKey })
167
+ return core.ready().catch(safetyCatch)
168
+ }
169
+ })
170
+
171
+ const sessions = []
172
+ for (const core of this.cores.values()) {
173
+ if (!core.opened) continue // If the core is not opened, it will be replicated in preload.
174
+ const session = core.session()
175
+ sessions.push(session)
176
+ core.replicate(stream)
177
+ }
178
+
179
+ const streamRecord = { stream, isExternal }
180
+ this._replicationStreams.push(streamRecord)
181
+ this._streamSessions.set(stream, sessions)
182
+
183
+ stream.once('close', () => {
184
+ this._replicationStreams.splice(this._replicationStreams.indexOf(streamRecord), 1)
185
+ this._streamSessions.delete(stream)
186
+ Promise.all(sessions.map(s => s.close())).catch(safetyCatch)
187
+ })
188
+ return stream
86
189
  }
87
190
 
88
- // Backup/Restore
191
+ namespace (name) {
192
+ if (!b4a.isBuffer(name)) name = b4a.from(name)
193
+ return new Corestore(this.storage, {
194
+ _namespace: generateNamespace(this._namespace, name),
195
+ _opening: this._opening,
196
+ _cores: this.cores,
197
+ _streams: this._replicationStreams,
198
+ _streamSessions: this._streamSessions,
199
+ keys: this._opening.then(() => this.keys)
200
+ })
201
+ }
89
202
 
90
- async backup () {
91
- if (!this.opened) await this.open()
92
- const allCores = await this._db.getAllCores()
93
- return {
94
- masterKey: this._loader.masterKey.toString('hex'),
95
- cores: allCores
203
+ async _close () {
204
+ await this._opening
205
+ if (!this._namespace.equals(DEFAULT_NAMESPACE)) return // namespaces should not release resources on close
206
+ const closePromises = []
207
+ for (const core of this.cores.values()) {
208
+ closePromises.push(core.close())
209
+ }
210
+ await Promise.allSettled(closePromises)
211
+ for (const { stream, isExternal } of this._replicationStreams) {
212
+ // Only close streams that were created by the Corestore
213
+ if (!isExternal) stream.destroy()
96
214
  }
215
+ await this.keys.close()
97
216
  }
98
217
 
99
- restore (manifest) {
100
- return this._db.restore(manifest)
218
+ close () {
219
+ if (this._closing) return this._closing
220
+ this._closing = this._close()
221
+ this._closing.catch(safetyCatch)
222
+ return this._closing
101
223
  }
102
224
 
103
- static async restore (manifest, target) {
104
- if (!manifest || !manifest.masterKey) throw new Error('Malformed manifest.')
105
- const store = new this(target, {
106
- overwriteMasterKey: true,
107
- masterKey: Buffer.from(manifest.masterKey, 'hex')
108
- })
109
- await store.restore(manifest)
110
- return store
225
+ static createToken () {
226
+ return KeyManager.createToken()
111
227
  }
112
228
  }
113
229
 
114
- function defaultStorage (dir) {
115
- return function (name) {
116
- let lock = null
117
- try {
118
- lock = name.endsWith('/bitfield') ? require('fd-lock') : null
119
- } catch (err) {}
120
- return raf(name, { directory: dir, lock: lock })
230
+ function validateGetOptions (opts) {
231
+ if (b4a.isBuffer(opts)) return { key: opts, publicKey: opts }
232
+ if (opts.key) {
233
+ opts.publicKey = opts.key
234
+ }
235
+ if (opts.keyPair) {
236
+ opts.publicKey = opts.keyPair.publicKey
237
+ opts.secretKey = opts.keyPair.secretKey
121
238
  }
239
+ if (opts.name && typeof opts.name !== 'string') throw new Error('name option must be a String')
240
+ if (opts.name && opts.secretKey) throw new Error('Cannot provide both a name and a secret key')
241
+ if (opts.publicKey && !b4a.isBuffer(opts.publicKey)) throw new Error('publicKey option must be a Buffer or Uint8Array')
242
+ if (opts.secretKey && !b4a.isBuffer(opts.secretKey)) throw new Error('secretKey option must be a Buffer or Uint8Array')
243
+ if (!opts._discoveryKey && (!opts.name && !opts.publicKey)) throw new Error('Must provide either a name or a publicKey')
244
+ return opts
245
+ }
246
+
247
+ function generateNamespace (first, second) {
248
+ if (!b4a.isBuffer(first)) first = b4a.from(first)
249
+ if (second && !b4a.isBuffer(second)) second = b4a.from(second)
250
+ const out = b4a.allocUnsafe(32)
251
+ sodium.crypto_generichash(out, second ? b4a.concat([first, second]) : first)
252
+ return out
253
+ }
254
+
255
+ function isStream (s) {
256
+ return typeof s === 'object' && s && typeof s.pipe === 'function'
122
257
  }
package/lib/keys.js ADDED
@@ -0,0 +1,110 @@
1
+ // TODO: Extract this into a standalone module
2
+
3
+ const sodium = require('sodium-universal')
4
+ const blake2b = require('blake2b-universal')
5
+ const b4a = require('b4a')
6
+
7
+ const DEFAULT_TOKEN = b4a.alloc(0)
8
+ const NAMESPACE = b4a.from('@hyperspace/key-manager')
9
+
10
+ module.exports = class KeyManager {
11
+ constructor (storage, profile, opts = {}) {
12
+ this.storage = storage
13
+ this.profile = profile
14
+ }
15
+
16
+ _sign (keyPair, message) {
17
+ if (!keyPair._secretKey) throw new Error('Invalid key pair')
18
+ const signature = b4a.allocUnsafe(sodium.crypto_sign_BYTES)
19
+ sodium.crypto_sign_detached(signature, message, keyPair._secretKey)
20
+ return signature
21
+ }
22
+
23
+ createSecret (name, token) {
24
+ return deriveSeed(this.profile, token, name)
25
+ }
26
+
27
+ createHypercoreKeyPair (name, token) {
28
+ const keyPair = {
29
+ publicKey: b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES),
30
+ _secretKey: b4a.alloc(sodium.crypto_sign_SECRETKEYBYTES),
31
+ auth: {
32
+ sign: (msg) => this._sign(keyPair, msg),
33
+ verify: (signable, signature) => {
34
+ return sodium.crypto_sign_detached(signature, signable, keyPair.publicKey)
35
+ }
36
+ }
37
+ }
38
+
39
+ sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair._secretKey, this.createSecret(name, token))
40
+
41
+ return keyPair
42
+ }
43
+
44
+ createNetworkIdentity (name, token) {
45
+ const keyPair = {
46
+ publicKey: b4a.alloc(32),
47
+ secretKey: b4a.alloc(64)
48
+ }
49
+
50
+ sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair.secretKey, this.createSecret(name, token))
51
+
52
+ return keyPair
53
+ }
54
+
55
+ close () {
56
+ return new Promise((resolve, reject) => {
57
+ this.storage.close(err => {
58
+ if (err) return reject(err)
59
+ return resolve()
60
+ })
61
+ })
62
+ }
63
+
64
+ static createToken () {
65
+ return randomBytes(32)
66
+ }
67
+
68
+ static async fromStorage (storage, opts = {}) {
69
+ const profileStorage = storage(opts.name || 'default')
70
+
71
+ const profile = await new Promise((resolve, reject) => {
72
+ profileStorage.stat((err, st) => {
73
+ if (err && err.code !== 'ENOENT') return reject(err)
74
+ if (err || st.size < 32 || opts.overwrite) {
75
+ const key = randomBytes(32)
76
+ return profileStorage.write(0, key, err => {
77
+ if (err) return reject(err)
78
+ return resolve(key)
79
+ })
80
+ }
81
+ profileStorage.read(0, 32, (err, key) => {
82
+ if (err) return reject(err)
83
+ return resolve(key)
84
+ })
85
+ })
86
+ })
87
+
88
+ return new this(profileStorage, profile, opts)
89
+ }
90
+ }
91
+
92
+ function deriveSeed (profile, token, name, output) {
93
+ if (token && token.length < 32) throw new Error('Token must be a Buffer with length >= 32')
94
+ if (!name || typeof name !== 'string') throw new Error('name must be a String')
95
+ if (!output) output = b4a.alloc(32)
96
+
97
+ blake2b.batch(output, [
98
+ NAMESPACE,
99
+ token || DEFAULT_TOKEN,
100
+ b4a.from(b4a.byteLength(name, 'ascii') + '\n' + name, 'ascii')
101
+ ], profile)
102
+
103
+ return output
104
+ }
105
+
106
+ function randomBytes (n) {
107
+ const buf = b4a.allocUnsafe(n)
108
+ sodium.randombytes_buf(buf)
109
+ return buf
110
+ }
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "corestore",
3
- "version": "6.0.0-beta2",
4
- "description": "A Hypercore factory that stores and replicates linked cores.",
3
+ "version": "6.0.1-alpha.12",
4
+ "description": "A Hypercore factory that simplifies managing collections of cores.",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "test": "standard && tape test/*.js"
7
+ "test": "standard && brittle test/*.js"
8
8
  },
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "git+https://github.com/andrewosh/corestore.git"
11
+ "url": "git+https://github.com/hypercore-protocol/corestore.git"
12
12
  },
13
13
  "keywords": [
14
14
  "corestore"
@@ -16,30 +16,23 @@
16
16
  "author": "Andrew Osheroff <andrewosh@gmail.com>",
17
17
  "license": "MIT",
18
18
  "bugs": {
19
- "url": "https://github.com/andrewosh/corestore/issues"
19
+ "url": "https://github.com/hypercore-protocol/corestore/issues"
20
20
  },
21
- "homepage": "https://github.com/andrewosh/corestore#readme",
21
+ "homepage": "https://github.com/hypercore-protocol/corestore#readme",
22
22
  "devDependencies": {
23
- "@andrewosh/corestore-migration": "^5.8.2",
24
- "hypercore-promisifier": "^1.0.3",
25
- "random-access-memory": "^3.1.1",
26
- "standard": "^16.0.3",
27
- "tape": "^5.0.1"
23
+ "brittle": "^1.6.0",
24
+ "random-access-file": "^2.2.0",
25
+ "random-access-memory": "^3.1.2",
26
+ "standardx": "^7.0.0",
27
+ "tmp-promise": "^3.0.2"
28
28
  },
29
29
  "dependencies": {
30
- "@jsdevtools/readdir-enhanced": "^6.0.4",
31
- "custom-error-class": "^1.0.0",
30
+ "b4a": "^1.3.1",
31
+ "blake2b-universal": "^1.0.1",
32
32
  "derive-key": "^1.0.1",
33
- "end-of-stream": "^1.4.4",
34
- "fd-lock": "^1.1.1",
35
- "hyperbee": "^1.1.1",
36
- "hypercore": "^9.6.0",
37
- "hypercore-crypto": "^2.2.0",
38
- "hypercore-protocol": "^8.0.7",
39
- "nanoresource-promise": "^2.0.0",
40
- "random-access-file": "^2.1.4",
41
- "random-access-storage": "^1.4.1",
42
- "refpool": "^1.2.2",
43
- "varint": "^6.0.0"
33
+ "hypercore": "next",
34
+ "hypercore-crypto": "^3.2.1",
35
+ "safety-catch": "^1.0.1",
36
+ "sodium-universal": "^3.0.4"
44
37
  }
45
38
  }