corestore 6.0.1-alpha.12 → 6.0.1-alpha.15

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
@@ -5,38 +5,91 @@ const sodium = require('sodium-universal')
5
5
  const Hypercore = require('hypercore')
6
6
  const b4a = require('b4a')
7
7
 
8
- const KeyManager = require('./lib/keys')
8
+ const [NS] = crypto.namespace('corestore', 1)
9
+ const DEFAULT_NAMESPACE = b4a.alloc(32) // This is meant to be 32 0-bytes
9
10
 
10
11
  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')
12
+ const PRIMARY_KEY_FILE_NAME = 'primary-key'
13
+ const USERDATA_NAME_KEY = 'corestore/name'
14
+ const USERDATA_NAMESPACE_KEY = 'corestore/namespace'
15
15
 
16
16
  module.exports = class Corestore extends EventEmitter {
17
17
  constructor (storage, opts = {}) {
18
18
  super()
19
19
 
20
- this.storage = Hypercore.defaultStorage(storage, { lock: PROFILES_DIR + '/default' })
21
-
20
+ this.storage = Hypercore.defaultStorage(storage, { lock: PRIMARY_KEY_FILE_NAME })
22
21
  this.cores = opts._cores || new Map()
23
- this.keys = opts.keys
22
+ this.primaryKey = null
24
23
 
25
- this._namespace = opts._namespace || DEFAULT_NAMESPACE
24
+ this._keyStorage = null
25
+ this._primaryKey = opts.primaryKey
26
+ this._namespace = opts.namespace || DEFAULT_NAMESPACE
26
27
  this._replicationStreams = opts._streams || []
28
+ this._overwrite = opts.overwrite === true
29
+
27
30
  this._streamSessions = opts._streamSessions || new Map()
31
+ this._sessions = new Set() // sessions for THIS namespace
32
+
33
+ this._findingPeersCount = 0
34
+ this._findingPeers = []
35
+
36
+ if (this._namespace.byteLength !== 32) throw new Error('Namespace must be a 32-byte Buffer or Uint8Array')
28
37
 
29
38
  this._opening = opts._opening ? opts._opening.then(() => this._open()) : this._open()
30
39
  this._opening.catch(safetyCatch)
31
40
  this.ready = () => this._opening
32
41
  }
33
42
 
43
+ findingPeers () {
44
+ let done = false
45
+ this._incFindingPeers()
46
+
47
+ return () => {
48
+ if (done) return
49
+ done = true
50
+ this._decFindingPeers()
51
+ }
52
+ }
53
+
54
+ _incFindingPeers () {
55
+ if (++this._findingPeersCount !== 1) return
56
+
57
+ for (const core of this._sessions) {
58
+ this._findingPeers.push(core.findingPeers())
59
+ }
60
+ }
61
+
62
+ _decFindingPeers () {
63
+ if (--this._findingPeersCount !== 0) return
64
+
65
+ while (this._findingPeers.length > 0) {
66
+ this._findingPeers.pop()()
67
+ }
68
+ }
69
+
34
70
  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))
71
+ if (this._primaryKey) {
72
+ this.primaryKey = await this._primaryKey
73
+ return this.primaryKey
39
74
  }
75
+ this._keyStorage = this.storage(PRIMARY_KEY_FILE_NAME)
76
+ this.primaryKey = await new Promise((resolve, reject) => {
77
+ this._keyStorage.stat((err, st) => {
78
+ if (err && err.code !== 'ENOENT') return reject(err)
79
+ if (err || st.size < 32 || this._overwrite) {
80
+ const key = crypto.randomBytes(32)
81
+ return this._keyStorage.write(0, key, err => {
82
+ if (err) return reject(err)
83
+ return resolve(key)
84
+ })
85
+ }
86
+ this._keyStorage.read(0, 32, (err, key) => {
87
+ if (err) return reject(err)
88
+ return resolve(key)
89
+ })
90
+ })
91
+ })
92
+ return this.primaryKey
40
93
  }
41
94
 
42
95
  async _generateKeys (opts) {
@@ -58,7 +111,7 @@ module.exports = class Corestore extends EventEmitter {
58
111
  discoveryKey: crypto.discoveryKey(opts.publicKey)
59
112
  }
60
113
  }
61
- const { publicKey, auth } = await this.keys.createHypercoreKeyPair(opts.name, this._namespace)
114
+ const { publicKey, auth } = await this.createKeyPair(opts.name)
62
115
  return {
63
116
  keyPair: {
64
117
  publicKey,
@@ -70,6 +123,7 @@ module.exports = class Corestore extends EventEmitter {
70
123
  }
71
124
 
72
125
  _getPrereadyUserData (core, key) {
126
+ // Need to manually read the header values before the Hypercore is ready, hence the ugliness.
73
127
  for (const { key: savedKey, value } of core.core.header.userData) {
74
128
  if (key === savedKey) return value
75
129
  }
@@ -81,7 +135,7 @@ module.exports = class Corestore extends EventEmitter {
81
135
  if (!name) return
82
136
 
83
137
  const namespace = this._getPrereadyUserData(core, USERDATA_NAMESPACE_KEY)
84
- const { publicKey, auth } = await this.keys.createHypercoreKeyPair(b4a.toString(name), namespace)
138
+ const { publicKey, auth } = await this.createKeyPair(b4a.toString(name), namespace)
85
139
  if (!b4a.equals(publicKey, core.key)) throw new Error('Stored core key does not match the provided name')
86
140
 
87
141
  // TODO: Should Hypercore expose a helper for this, or should preready return keypair/auth?
@@ -148,6 +202,26 @@ module.exports = class Corestore extends EventEmitter {
148
202
  return { from: core, keyPair, auth }
149
203
  }
150
204
 
205
+ async createKeyPair (name) {
206
+ if (!this.primaryKey) await this._opening
207
+
208
+ const keyPair = {
209
+ publicKey: b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES),
210
+ secretKey: b4a.alloc(sodium.crypto_sign_SECRETKEYBYTES),
211
+ auth: {
212
+ sign: (msg) => sign(keyPair, msg),
213
+ verify: (signable, signature) => {
214
+ return sodium.crypto_sign_detached(signature, signable, keyPair.publicKey)
215
+ }
216
+ }
217
+ }
218
+
219
+ const seed = deriveSeed(this.primaryKey, this._namespace, name)
220
+ sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
221
+
222
+ return keyPair
223
+ }
224
+
151
225
  get (opts = {}) {
152
226
  opts = validateGetOptions(opts)
153
227
  const core = new Hypercore(null, {
@@ -155,6 +229,19 @@ module.exports = class Corestore extends EventEmitter {
155
229
  name: null,
156
230
  preload: () => this._preload(opts)
157
231
  })
232
+
233
+ this._sessions.add(core)
234
+ if (this._findingPeersCount > 0) {
235
+ this._findingPeers.push(core.findingPeers())
236
+ }
237
+
238
+ core.once('close', () => {
239
+ // technically better to also clear _findingPeers if we added it,
240
+ // but the lifecycle for those are pretty short so prob not worth the complexity
241
+ // as _decFindingPeers clear them all.
242
+ this._sessions.delete(core)
243
+ })
244
+
158
245
  return core
159
246
  }
160
247
 
@@ -189,20 +276,23 @@ module.exports = class Corestore extends EventEmitter {
189
276
  }
190
277
 
191
278
  namespace (name) {
192
- if (!b4a.isBuffer(name)) name = b4a.from(name)
193
279
  return new Corestore(this.storage, {
194
- _namespace: generateNamespace(this._namespace, name),
280
+ primaryKey: this._opening.then(() => this.primaryKey),
281
+ namespace: generateNamespace(this._namespace, name),
195
282
  _opening: this._opening,
196
283
  _cores: this.cores,
197
284
  _streams: this._replicationStreams,
198
- _streamSessions: this._streamSessions,
199
- keys: this._opening.then(() => this.keys)
285
+ _streamSessions: this._streamSessions
200
286
  })
201
287
  }
202
288
 
203
289
  async _close () {
204
290
  await this._opening
205
- if (!this._namespace.equals(DEFAULT_NAMESPACE)) return // namespaces should not release resources on close
291
+ if (!b4a.equals(this._namespace, DEFAULT_NAMESPACE)) {
292
+ // namespaces should not release resources on close
293
+ // TODO: Refactor the namespace close logic to actually close sessions with ref counting
294
+ return
295
+ }
206
296
  const closePromises = []
207
297
  for (const core of this.cores.values()) {
208
298
  closePromises.push(core.close())
@@ -212,7 +302,13 @@ module.exports = class Corestore extends EventEmitter {
212
302
  // Only close streams that were created by the Corestore
213
303
  if (!isExternal) stream.destroy()
214
304
  }
215
- await this.keys.close()
305
+ if (!this._keyStorage) return
306
+ await new Promise((resolve, reject) => {
307
+ this._keyStorage.close(err => {
308
+ if (err) return reject(err)
309
+ return resolve(null)
310
+ })
311
+ })
216
312
  }
217
313
 
218
314
  close () {
@@ -221,10 +317,13 @@ module.exports = class Corestore extends EventEmitter {
221
317
  this._closing.catch(safetyCatch)
222
318
  return this._closing
223
319
  }
320
+ }
224
321
 
225
- static createToken () {
226
- return KeyManager.createToken()
227
- }
322
+ function sign (keyPair, message) {
323
+ if (!keyPair.secretKey) throw new Error('Invalid key pair')
324
+ const signature = b4a.allocUnsafe(sodium.crypto_sign_BYTES)
325
+ sodium.crypto_sign_detached(signature, message, keyPair.secretKey)
326
+ return signature
228
327
  }
229
328
 
230
329
  function validateGetOptions (opts) {
@@ -244,11 +343,17 @@ function validateGetOptions (opts) {
244
343
  return opts
245
344
  }
246
345
 
247
- function generateNamespace (first, second) {
248
- if (!b4a.isBuffer(first)) first = b4a.from(first)
249
- if (second && !b4a.isBuffer(second)) second = b4a.from(second)
346
+ function generateNamespace (namespace, name) {
347
+ if (!b4a.isBuffer(name)) name = b4a.from(name)
250
348
  const out = b4a.allocUnsafe(32)
251
- sodium.crypto_generichash(out, second ? b4a.concat([first, second]) : first)
349
+ sodium.crypto_generichash_batch(out, [namespace, name])
350
+ return out
351
+ }
352
+
353
+ function deriveSeed (primaryKey, namespace, name) {
354
+ if (!b4a.isBuffer(name)) name = b4a.from(name)
355
+ const out = b4a.alloc(32)
356
+ sodium.crypto_generichash_batch(out, [NS, namespace, name], primaryKey)
252
357
  return out
253
358
  }
254
359
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "corestore",
3
- "version": "6.0.1-alpha.12",
3
+ "version": "6.0.1-alpha.15",
4
4
  "description": "A Hypercore factory that simplifies managing collections of cores.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -20,16 +20,13 @@
20
20
  },
21
21
  "homepage": "https://github.com/hypercore-protocol/corestore#readme",
22
22
  "devDependencies": {
23
- "brittle": "^1.6.0",
23
+ "brittle": "^2.2.10",
24
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"
25
+ "random-access-memory": "^4.0.0",
26
+ "standardx": "^7.0.0"
28
27
  },
29
28
  "dependencies": {
30
29
  "b4a": "^1.3.1",
31
- "blake2b-universal": "^1.0.1",
32
- "derive-key": "^1.0.1",
33
30
  "hypercore": "next",
34
31
  "hypercore-crypto": "^3.2.1",
35
32
  "safety-catch": "^1.0.1",
package/test/all.js CHANGED
@@ -1,7 +1,10 @@
1
1
  const { test, configure } = require('brittle')
2
2
  const crypto = require('hypercore-crypto')
3
3
  const ram = require('random-access-memory')
4
- const tmp = require('tmp-promise')
4
+ const os = require('os')
5
+ const path = require('path')
6
+ const b4a = require('b4a')
7
+ const sodium = require('sodium-universal')
5
8
 
6
9
  const Corestore = require('..')
7
10
 
@@ -99,8 +102,8 @@ test('replicating cores created after replication begins', async function (t) {
99
102
  })
100
103
 
101
104
  test('replicating cores using discovery key hook', async function (t) {
102
- const dir = await tmp.dir({ unsafeCleanup: true })
103
- let store1 = new Corestore(dir.path)
105
+ const dir = tmpdir()
106
+ let store1 = new Corestore(dir)
104
107
  const store2 = new Corestore(ram)
105
108
 
106
109
  const core = store1.get({ name: 'main' })
@@ -108,15 +111,13 @@ test('replicating cores using discovery key hook', async function (t) {
108
111
  const key = core.key
109
112
 
110
113
  await store1.close()
111
- store1 = new Corestore(dir.path)
114
+ store1 = new Corestore(dir)
112
115
 
113
116
  const s = store1.replicate(true, { live: true })
114
117
  s.pipe(store2.replicate(false, { live: true })).pipe(s)
115
118
 
116
119
  const core2 = store2.get(key)
117
120
  t.alike(await core2.get(0), Buffer.from('hello'))
118
-
119
- await dir.cleanup()
120
121
  })
121
122
 
122
123
  test('nested namespaces', async function (t) {
@@ -144,9 +145,9 @@ test('core uncached when all sessions close', async function (t) {
144
145
  })
145
146
 
146
147
  test('writable core loaded from name userData', async function (t) {
147
- const dir = await tmp.dir({ unsafeCleanup: true })
148
+ const dir = tmpdir()
148
149
 
149
- let store = new Corestore(dir.path)
150
+ let store = new Corestore(dir)
150
151
  let core = store.get({ name: 'main' })
151
152
  await core.ready()
152
153
  const key = core.key
@@ -156,7 +157,7 @@ test('writable core loaded from name userData', async function (t) {
156
157
  t.is(core.length, 1)
157
158
 
158
159
  await store.close()
159
- store = new Corestore(dir.path)
160
+ store = new Corestore(dir)
160
161
  core = store.get(key)
161
162
  await core.ready()
162
163
 
@@ -165,25 +166,21 @@ test('writable core loaded from name userData', async function (t) {
165
166
  t.is(core.length, 2)
166
167
  t.alike(await core.get(0), Buffer.from('hello'))
167
168
  t.alike(await core.get(1), Buffer.from('world'))
168
-
169
- await dir.cleanup()
170
169
  })
171
170
 
172
171
  test('storage locking', async function (t) {
173
- const dir = await tmp.dir({ unsafeCleanup: true })
172
+ const dir = tmpdir()
174
173
 
175
- const store1 = new Corestore(dir.path)
174
+ const store1 = new Corestore(dir)
176
175
  await store1.ready()
177
176
 
178
- const store2 = new Corestore(dir.path)
177
+ const store2 = new Corestore(dir)
179
178
  try {
180
179
  await store2.ready()
181
180
  t.fail('dir should have been locked')
182
181
  } catch {
183
182
  t.pass('dir was locked')
184
183
  }
185
-
186
- await dir.cleanup()
187
184
  })
188
185
 
189
186
  test('closing a namespace does not close cores', async function (t) {
@@ -205,3 +202,73 @@ test('closing a namespace does not close cores', async function (t) {
205
202
  t.ok(core1.closed)
206
203
  t.ok(core2.closed)
207
204
  })
205
+
206
+ test('findingPeers', async function (t) {
207
+ t.plan(6)
208
+
209
+ const store = new Corestore(ram)
210
+
211
+ const ns1 = store.namespace('ns1')
212
+ const ns2 = store.namespace('ns2')
213
+
214
+ const a = ns1.get(Buffer.alloc(32).fill('a'))
215
+ const b = ns2.get(Buffer.alloc(32).fill('b'))
216
+
217
+ const done = ns1.findingPeers()
218
+
219
+ let aUpdated = false
220
+ let bUpdated = false
221
+ let cUpdated = false
222
+
223
+ const c = ns1.get(Buffer.alloc(32).fill('c'))
224
+
225
+ a.update().then(function (bool) {
226
+ aUpdated = true
227
+ })
228
+
229
+ b.update().then(function (bool) {
230
+ bUpdated = true
231
+ })
232
+
233
+ c.update().then(function (bool) {
234
+ cUpdated = true
235
+ })
236
+
237
+ await new Promise(resolve => setImmediate(resolve))
238
+
239
+ t.is(aUpdated, false)
240
+ t.is(bUpdated, true)
241
+ t.is(cUpdated, false)
242
+
243
+ done()
244
+
245
+ await new Promise(resolve => setImmediate(resolve))
246
+
247
+ t.is(aUpdated, true)
248
+ t.is(bUpdated, true)
249
+ t.is(cUpdated, true)
250
+ })
251
+
252
+ test('different primary keys yield different keypairs', async function (t) {
253
+ const pk1 = randomBytes(32)
254
+ const pk2 = randomBytes(32)
255
+ t.unlike(pk1, pk2)
256
+
257
+ const store1 = new Corestore(ram, { primaryKey: pk1 })
258
+ const store2 = new Corestore(ram, { primaryKey: pk2 })
259
+
260
+ const kp1 = await store1.createKeyPair('hello')
261
+ const kp2 = await store2.createKeyPair('hello')
262
+
263
+ t.unlike(kp1.publicKey, kp2.publicKey)
264
+ })
265
+
266
+ function tmpdir () {
267
+ return path.join(os.tmpdir(), 'corestore-' + Math.random().toString(16).slice(2))
268
+ }
269
+
270
+ function randomBytes (n) {
271
+ const buf = b4a.allocUnsafe(n)
272
+ sodium.randombytes_buf(buf)
273
+ return buf
274
+ }
package/lib/keys.js DELETED
@@ -1,110 +0,0 @@
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/test/keys.js DELETED
@@ -1,65 +0,0 @@
1
- const p = require('path')
2
- const fs = require('fs')
3
-
4
- const test = require('brittle')
5
- const ram = require('random-access-memory')
6
- const raf = require('random-access-file')
7
-
8
- const KeyManager = require('../lib/keys')
9
-
10
- test('can create hypercore keypairs', async t => {
11
- const keys = await KeyManager.fromStorage(ram)
12
-
13
- const kp1 = await keys.createHypercoreKeyPair('core1')
14
- const kp2 = await keys.createHypercoreKeyPair('core2')
15
-
16
- t.is(kp1.publicKey.length, 32)
17
- t.is(kp2.publicKey.length, 32)
18
- t.unlike(kp1.publicKey, kp2.publicKey)
19
- })
20
-
21
- test('distinct tokens create distinct hypercore keypairs', async t => {
22
- const keys = await KeyManager.fromStorage(ram)
23
- const token1 = KeyManager.createToken()
24
- const token2 = KeyManager.createToken()
25
-
26
- const kp1 = await keys.createHypercoreKeyPair('core1', token1)
27
- const kp2 = await keys.createHypercoreKeyPair('core1', token2)
28
-
29
- t.unlike(kp1.publicKey, kp2.publicKey)
30
- })
31
-
32
- test('short user-provided token will throw', async t => {
33
- const keys = await KeyManager.fromStorage(ram)
34
-
35
- try {
36
- await keys.createHypercoreKeyPair('core1', Buffer.from('hello'))
37
- t.fail('did not throw')
38
- } catch {
39
- t.pass('threw correctly')
40
- }
41
- })
42
-
43
- test('persistent storage regenerates keys correctly', async t => {
44
- const testPath = p.resolve(__dirname, 'test-data')
45
-
46
- const keys1 = await KeyManager.fromStorage((name) => raf(testPath, { directory: testPath }))
47
- const kp1 = await keys1.createHypercoreKeyPair('core1')
48
-
49
- const keys2 = await KeyManager.fromStorage((name) => raf(testPath, { directory: testPath }))
50
- const kp2 = await keys2.createHypercoreKeyPair('core1')
51
-
52
- t.alike(kp1.publicKey, kp2.publicKey)
53
-
54
- await fs.promises.rm(testPath, { recursive: true })
55
- })
56
-
57
- test('different master keys -> different keys', async t => {
58
- const keys1 = await KeyManager.fromStorage(ram)
59
- const keys2 = await KeyManager.fromStorage(ram)
60
-
61
- const kp1 = await keys1.createHypercoreKeyPair('core1')
62
- const kp2 = await keys2.createHypercoreKeyPair('core1')
63
-
64
- t.unlike(kp1.publicKey, kp2.publicKey)
65
- })