corestore 6.0.1-alpha.8 → 6.0.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/.github/workflows/test-node.yml +5 -6
- package/README.md +16 -4
- package/index.js +206 -69
- package/package.json +10 -11
- package/test/all.js +198 -9
- package/test/cache.js +46 -0
- package/lib/keys.js +0 -104
- package/test/keys.js +0 -65
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
name:
|
|
2
|
-
|
|
1
|
+
name: Build Status
|
|
3
2
|
on:
|
|
4
3
|
push:
|
|
5
4
|
branches:
|
|
6
|
-
-
|
|
5
|
+
- master
|
|
7
6
|
pull_request:
|
|
8
7
|
branches:
|
|
9
|
-
-
|
|
8
|
+
- master
|
|
10
9
|
jobs:
|
|
11
10
|
build:
|
|
12
11
|
strategy:
|
|
13
12
|
matrix:
|
|
14
|
-
node-version: [
|
|
13
|
+
node-version: [lts/*]
|
|
15
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@
|
|
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,4 +1,4 @@
|
|
|
1
|
-
# Corestore
|
|
1
|
+
# Corestore
|
|
2
2
|
|
|
3
3
|
Corestore is a Hypercore factory that makes it easier to manage large collections of named Hypercores.
|
|
4
4
|
|
|
@@ -9,7 +9,7 @@ Corestore provides:
|
|
|
9
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
10
|
|
|
11
11
|
### Installation
|
|
12
|
-
`npm install corestore
|
|
12
|
+
`npm install corestore`
|
|
13
13
|
|
|
14
14
|
### Usage
|
|
15
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:
|
|
@@ -34,14 +34,26 @@ If that Hypercore has previously been loaded, subsequent calls to `get` will ret
|
|
|
34
34
|
|
|
35
35
|
All other options besides `name` and `key` will be forwarded to the Hypercore constructor.
|
|
36
36
|
|
|
37
|
-
#### `const stream = store.replicate(
|
|
37
|
+
#### `const stream = store.replicate(optsOrStream)`
|
|
38
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
39
|
|
|
40
40
|
`opts` will be forwarded to Hypercore's `replicate` function.
|
|
41
41
|
|
|
42
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
43
|
|
|
44
|
-
If the remote side dynamically adds a new Hypercore to the replication stream, Corestore will load and
|
|
44
|
+
If the remote side dynamically adds a new Hypercore to the replication stream, Corestore will load and replicate that core if possible.
|
|
45
|
+
|
|
46
|
+
Using [Hyperswarm](https://github.com/hyperswarm/hyperswarm) you can easily replicate corestores
|
|
47
|
+
|
|
48
|
+
``` js
|
|
49
|
+
const swarm = new Hyperswarm()
|
|
50
|
+
|
|
51
|
+
// join the relevant topic
|
|
52
|
+
swarm.join(...)
|
|
53
|
+
|
|
54
|
+
// simply pass the connection stream to corestore
|
|
55
|
+
swarm.on('connection', (connection) => store.replicate(connection))
|
|
56
|
+
```
|
|
45
57
|
|
|
46
58
|
#### `const store = store.namespace(name)`
|
|
47
59
|
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.
|
package/index.js
CHANGED
|
@@ -1,47 +1,105 @@
|
|
|
1
1
|
const { EventEmitter } = require('events')
|
|
2
|
+
const safetyCatch = require('safety-catch')
|
|
2
3
|
const crypto = require('hypercore-crypto')
|
|
3
4
|
const sodium = require('sodium-universal')
|
|
4
5
|
const Hypercore = require('hypercore')
|
|
6
|
+
const Xache = require('xache')
|
|
7
|
+
const b4a = require('b4a')
|
|
5
8
|
|
|
6
|
-
const
|
|
9
|
+
const [NS] = crypto.namespace('corestore', 1)
|
|
10
|
+
const DEFAULT_NAMESPACE = b4a.alloc(32) // This is meant to be 32 0-bytes
|
|
7
11
|
|
|
8
12
|
const CORES_DIR = 'cores'
|
|
9
|
-
const
|
|
10
|
-
const USERDATA_NAME_KEY = '
|
|
11
|
-
const USERDATA_NAMESPACE_KEY = '
|
|
12
|
-
const DEFAULT_NAMESPACE = generateNamespace('@corestore/default')
|
|
13
|
+
const PRIMARY_KEY_FILE_NAME = 'primary-key'
|
|
14
|
+
const USERDATA_NAME_KEY = 'corestore/name'
|
|
15
|
+
const USERDATA_NAMESPACE_KEY = 'corestore/namespace'
|
|
13
16
|
|
|
14
17
|
module.exports = class Corestore extends EventEmitter {
|
|
15
18
|
constructor (storage, opts = {}) {
|
|
16
19
|
super()
|
|
17
20
|
|
|
18
|
-
this.storage = Hypercore.defaultStorage(storage, { lock:
|
|
19
|
-
|
|
21
|
+
this.storage = Hypercore.defaultStorage(storage, { lock: PRIMARY_KEY_FILE_NAME })
|
|
20
22
|
this.cores = opts._cores || new Map()
|
|
21
|
-
this.
|
|
23
|
+
this.primaryKey = null
|
|
24
|
+
this.cache = !!opts.cache
|
|
22
25
|
|
|
23
|
-
this.
|
|
26
|
+
this._keyStorage = null
|
|
27
|
+
this._primaryKey = opts.primaryKey
|
|
28
|
+
this._namespace = opts.namespace || DEFAULT_NAMESPACE
|
|
24
29
|
this._replicationStreams = opts._streams || []
|
|
30
|
+
this._overwrite = opts.overwrite === true
|
|
31
|
+
|
|
32
|
+
this._streamSessions = opts._streamSessions || new Map()
|
|
33
|
+
this._sessions = new Set() // sessions for THIS namespace
|
|
34
|
+
|
|
35
|
+
this._findingPeersCount = 0
|
|
36
|
+
this._findingPeers = []
|
|
37
|
+
|
|
38
|
+
if (this._namespace.byteLength !== 32) throw new Error('Namespace must be a 32-byte Buffer or Uint8Array')
|
|
25
39
|
|
|
26
40
|
this._opening = opts._opening ? opts._opening.then(() => this._open()) : this._open()
|
|
27
|
-
this._opening.catch(
|
|
41
|
+
this._opening.catch(safetyCatch)
|
|
28
42
|
this.ready = () => this._opening
|
|
29
43
|
}
|
|
30
44
|
|
|
45
|
+
findingPeers () {
|
|
46
|
+
let done = false
|
|
47
|
+
this._incFindingPeers()
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
if (done) return
|
|
51
|
+
done = true
|
|
52
|
+
this._decFindingPeers()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_incFindingPeers () {
|
|
57
|
+
if (++this._findingPeersCount !== 1) return
|
|
58
|
+
|
|
59
|
+
for (const core of this._sessions) {
|
|
60
|
+
this._findingPeers.push(core.findingPeers())
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_decFindingPeers () {
|
|
65
|
+
if (--this._findingPeersCount !== 0) return
|
|
66
|
+
|
|
67
|
+
while (this._findingPeers.length > 0) {
|
|
68
|
+
this._findingPeers.pop()()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
31
72
|
async _open () {
|
|
32
|
-
if (this.
|
|
33
|
-
this.
|
|
34
|
-
|
|
35
|
-
this.keys = await KeyManager.fromStorage(p => this.storage(PROFILES_DIR + '/' + p))
|
|
73
|
+
if (this._primaryKey) {
|
|
74
|
+
this.primaryKey = await this._primaryKey
|
|
75
|
+
return this.primaryKey
|
|
36
76
|
}
|
|
77
|
+
this._keyStorage = this.storage(PRIMARY_KEY_FILE_NAME)
|
|
78
|
+
this.primaryKey = await new Promise((resolve, reject) => {
|
|
79
|
+
this._keyStorage.stat((err, st) => {
|
|
80
|
+
if (err && err.code !== 'ENOENT') return reject(err)
|
|
81
|
+
if (err || st.size < 32 || this._overwrite) {
|
|
82
|
+
const key = crypto.randomBytes(32)
|
|
83
|
+
return this._keyStorage.write(0, key, err => {
|
|
84
|
+
if (err) return reject(err)
|
|
85
|
+
return resolve(key)
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
this._keyStorage.read(0, 32, (err, key) => {
|
|
89
|
+
if (err) return reject(err)
|
|
90
|
+
return resolve(key)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
return this.primaryKey
|
|
37
95
|
}
|
|
38
96
|
|
|
39
97
|
async _generateKeys (opts) {
|
|
40
|
-
if (opts.
|
|
98
|
+
if (opts._discoveryKey) {
|
|
41
99
|
return {
|
|
42
100
|
keyPair: null,
|
|
43
|
-
|
|
44
|
-
discoveryKey: opts.
|
|
101
|
+
auth: null,
|
|
102
|
+
discoveryKey: opts._discoveryKey
|
|
45
103
|
}
|
|
46
104
|
}
|
|
47
105
|
if (!opts.name) {
|
|
@@ -51,21 +109,23 @@ module.exports = class Corestore extends EventEmitter {
|
|
|
51
109
|
secretKey: opts.secretKey
|
|
52
110
|
},
|
|
53
111
|
sign: opts.sign,
|
|
112
|
+
auth: opts.auth,
|
|
54
113
|
discoveryKey: crypto.discoveryKey(opts.publicKey)
|
|
55
114
|
}
|
|
56
115
|
}
|
|
57
|
-
const { publicKey,
|
|
116
|
+
const { publicKey, auth } = await this.createKeyPair(opts.name)
|
|
58
117
|
return {
|
|
59
118
|
keyPair: {
|
|
60
119
|
publicKey,
|
|
61
120
|
secretKey: null
|
|
62
121
|
},
|
|
63
|
-
|
|
122
|
+
auth,
|
|
64
123
|
discoveryKey: crypto.discoveryKey(publicKey)
|
|
65
124
|
}
|
|
66
125
|
}
|
|
67
126
|
|
|
68
127
|
_getPrereadyUserData (core, key) {
|
|
128
|
+
// Need to manually read the header values before the Hypercore is ready, hence the ugliness.
|
|
69
129
|
for (const { key: savedKey, value } of core.core.header.userData) {
|
|
70
130
|
if (key === savedKey) return value
|
|
71
131
|
}
|
|
@@ -77,11 +137,11 @@ module.exports = class Corestore extends EventEmitter {
|
|
|
77
137
|
if (!name) return
|
|
78
138
|
|
|
79
139
|
const namespace = this._getPrereadyUserData(core, USERDATA_NAMESPACE_KEY)
|
|
80
|
-
const { publicKey,
|
|
81
|
-
if (!
|
|
140
|
+
const { publicKey, auth } = await this.createKeyPair(b4a.toString(name), namespace)
|
|
141
|
+
if (!b4a.equals(publicKey, core.key)) throw new Error('Stored core key does not match the provided name')
|
|
82
142
|
|
|
83
|
-
// TODO: Should Hypercore expose a helper for this, or should preready return keypair/
|
|
84
|
-
core.
|
|
143
|
+
// TODO: Should Hypercore expose a helper for this, or should preready return keypair/auth?
|
|
144
|
+
core.auth = auth
|
|
85
145
|
core.key = publicKey
|
|
86
146
|
core.writable = true
|
|
87
147
|
}
|
|
@@ -89,20 +149,22 @@ module.exports = class Corestore extends EventEmitter {
|
|
|
89
149
|
async _preload (opts) {
|
|
90
150
|
await this.ready()
|
|
91
151
|
|
|
92
|
-
const { discoveryKey, keyPair,
|
|
93
|
-
const id =
|
|
152
|
+
const { discoveryKey, keyPair, auth } = await this._generateKeys(opts)
|
|
153
|
+
const id = b4a.toString(discoveryKey, 'hex')
|
|
94
154
|
|
|
95
155
|
while (this.cores.has(id)) {
|
|
96
156
|
const existing = this.cores.get(id)
|
|
97
|
-
if (existing) {
|
|
98
|
-
|
|
157
|
+
if (existing.opened && !existing.closing) return { from: existing, keyPair, auth }
|
|
158
|
+
if (existing.closing) {
|
|
99
159
|
await existing.close()
|
|
160
|
+
} else {
|
|
161
|
+
await existing.ready().catch(safetyCatch)
|
|
100
162
|
}
|
|
101
163
|
}
|
|
102
164
|
|
|
103
165
|
const userData = {}
|
|
104
166
|
if (opts.name) {
|
|
105
|
-
userData[USERDATA_NAME_KEY] =
|
|
167
|
+
userData[USERDATA_NAME_KEY] = b4a.from(opts.name)
|
|
106
168
|
userData[USERDATA_NAMESPACE_KEY] = this._namespace
|
|
107
169
|
}
|
|
108
170
|
|
|
@@ -110,75 +172,136 @@ module.exports = class Corestore extends EventEmitter {
|
|
|
110
172
|
|
|
111
173
|
const storageRoot = [CORES_DIR, id.slice(0, 2), id.slice(2, 4), id].join('/')
|
|
112
174
|
const core = new Hypercore(p => this.storage(storageRoot + '/' + p), {
|
|
175
|
+
_preready: this._preready.bind(this),
|
|
113
176
|
autoClose: true,
|
|
114
177
|
encryptionKey: opts.encryptionKey || null,
|
|
115
|
-
keyPair: {
|
|
116
|
-
publicKey: keyPair.publicKey,
|
|
117
|
-
secretKey: null
|
|
118
|
-
},
|
|
119
178
|
userData,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
createIfMissing:
|
|
179
|
+
auth,
|
|
180
|
+
cache: opts.cache,
|
|
181
|
+
createIfMissing: !opts._discoveryKey,
|
|
182
|
+
keyPair: keyPair && keyPair.publicKey
|
|
183
|
+
? {
|
|
184
|
+
publicKey: keyPair.publicKey,
|
|
185
|
+
secretKey: null
|
|
186
|
+
}
|
|
187
|
+
: null
|
|
123
188
|
})
|
|
124
189
|
|
|
125
190
|
this.cores.set(id, core)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
191
|
+
core.ready().then(() => {
|
|
192
|
+
for (const { stream } of this._replicationStreams) {
|
|
193
|
+
const sessions = this._streamSessions.get(stream)
|
|
194
|
+
const session = core.session()
|
|
195
|
+
sessions.push(session)
|
|
196
|
+
core.replicate(stream)
|
|
197
|
+
}
|
|
198
|
+
}, () => {
|
|
199
|
+
this.cores.delete(id)
|
|
200
|
+
})
|
|
129
201
|
core.once('close', () => {
|
|
130
202
|
this.cores.delete(id)
|
|
131
203
|
})
|
|
132
204
|
|
|
133
|
-
return { from: core, keyPair,
|
|
205
|
+
return { from: core, keyPair, auth }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async createKeyPair (name, namespace = this._namespace) {
|
|
209
|
+
if (!this.primaryKey) await this._opening
|
|
210
|
+
|
|
211
|
+
const keyPair = {
|
|
212
|
+
publicKey: b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES),
|
|
213
|
+
secretKey: b4a.alloc(sodium.crypto_sign_SECRETKEYBYTES),
|
|
214
|
+
auth: {
|
|
215
|
+
sign: (msg) => sign(keyPair, msg),
|
|
216
|
+
verify: (signable, signature) => {
|
|
217
|
+
return crypto.verify(signable, signature, keyPair.publicKey)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const seed = deriveSeed(this.primaryKey, namespace, name)
|
|
223
|
+
sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
|
|
224
|
+
|
|
225
|
+
return keyPair
|
|
134
226
|
}
|
|
135
227
|
|
|
136
228
|
get (opts = {}) {
|
|
137
229
|
opts = validateGetOptions(opts)
|
|
230
|
+
|
|
231
|
+
if (opts.cache !== false) {
|
|
232
|
+
opts.cache = opts.cache === true || (this.cache && !opts.cache) ? defaultCache() : opts.cache
|
|
233
|
+
}
|
|
234
|
+
|
|
138
235
|
const core = new Hypercore(null, {
|
|
139
236
|
...opts,
|
|
140
237
|
name: null,
|
|
141
238
|
preload: () => this._preload(opts)
|
|
142
239
|
})
|
|
240
|
+
|
|
241
|
+
this._sessions.add(core)
|
|
242
|
+
if (this._findingPeersCount > 0) {
|
|
243
|
+
this._findingPeers.push(core.findingPeers())
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
core.once('close', () => {
|
|
247
|
+
// technically better to also clear _findingPeers if we added it,
|
|
248
|
+
// but the lifecycle for those are pretty short so prob not worth the complexity
|
|
249
|
+
// as _decFindingPeers clear them all.
|
|
250
|
+
this._sessions.delete(core)
|
|
251
|
+
})
|
|
252
|
+
|
|
143
253
|
return core
|
|
144
254
|
}
|
|
145
255
|
|
|
146
256
|
replicate (isInitiator, opts) {
|
|
147
257
|
const isExternal = isStream(isInitiator) || !!(opts && opts.stream)
|
|
148
|
-
const stream = Hypercore.createProtocolStream(isInitiator,
|
|
258
|
+
const stream = Hypercore.createProtocolStream(isInitiator, {
|
|
259
|
+
...opts,
|
|
260
|
+
ondiscoverykey: discoveryKey => {
|
|
261
|
+
const core = this.get({ _discoveryKey: discoveryKey })
|
|
262
|
+
return core.ready().catch(safetyCatch)
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
const sessions = []
|
|
149
267
|
for (const core of this.cores.values()) {
|
|
268
|
+
if (!core.opened) continue // If the core is not opened, it will be replicated in preload.
|
|
269
|
+
const session = core.session()
|
|
270
|
+
sessions.push(session)
|
|
150
271
|
core.replicate(stream)
|
|
151
272
|
}
|
|
152
|
-
|
|
153
|
-
const core = this.get({ discoveryKey })
|
|
154
|
-
core.ready().then(() => {
|
|
155
|
-
core.replicate(stream)
|
|
156
|
-
}, () => {
|
|
157
|
-
stream.close(discoveryKey)
|
|
158
|
-
})
|
|
159
|
-
})
|
|
273
|
+
|
|
160
274
|
const streamRecord = { stream, isExternal }
|
|
161
275
|
this._replicationStreams.push(streamRecord)
|
|
276
|
+
this._streamSessions.set(stream, sessions)
|
|
277
|
+
|
|
162
278
|
stream.once('close', () => {
|
|
163
279
|
this._replicationStreams.splice(this._replicationStreams.indexOf(streamRecord), 1)
|
|
280
|
+
this._streamSessions.delete(stream)
|
|
281
|
+
Promise.all(sessions.map(s => s.close())).catch(safetyCatch)
|
|
164
282
|
})
|
|
165
283
|
return stream
|
|
166
284
|
}
|
|
167
285
|
|
|
168
286
|
namespace (name) {
|
|
169
|
-
if (!Buffer.isBuffer(name)) name = Buffer.from(name)
|
|
170
287
|
return new Corestore(this.storage, {
|
|
171
|
-
|
|
288
|
+
primaryKey: this._opening.then(() => this.primaryKey),
|
|
289
|
+
namespace: generateNamespace(this._namespace, name),
|
|
290
|
+
cache: this.cache,
|
|
172
291
|
_opening: this._opening,
|
|
173
292
|
_cores: this.cores,
|
|
174
293
|
_streams: this._replicationStreams,
|
|
175
|
-
|
|
294
|
+
_streamSessions: this._streamSessions
|
|
176
295
|
})
|
|
177
296
|
}
|
|
178
297
|
|
|
179
298
|
async _close () {
|
|
180
|
-
if (this._closing) return this._closing
|
|
181
299
|
await this._opening
|
|
300
|
+
if (!b4a.equals(this._namespace, DEFAULT_NAMESPACE)) {
|
|
301
|
+
// namespaces should not release resources on close
|
|
302
|
+
// TODO: Refactor the namespace close logic to actually close sessions with ref counting
|
|
303
|
+
return
|
|
304
|
+
}
|
|
182
305
|
const closePromises = []
|
|
183
306
|
for (const core of this.cores.values()) {
|
|
184
307
|
closePromises.push(core.close())
|
|
@@ -188,23 +311,30 @@ module.exports = class Corestore extends EventEmitter {
|
|
|
188
311
|
// Only close streams that were created by the Corestore
|
|
189
312
|
if (!isExternal) stream.destroy()
|
|
190
313
|
}
|
|
191
|
-
|
|
314
|
+
if (!this._keyStorage) return
|
|
315
|
+
await new Promise((resolve, reject) => {
|
|
316
|
+
this._keyStorage.close(err => {
|
|
317
|
+
if (err) return reject(err)
|
|
318
|
+
return resolve(null)
|
|
319
|
+
})
|
|
320
|
+
})
|
|
192
321
|
}
|
|
193
322
|
|
|
194
323
|
close () {
|
|
195
324
|
if (this._closing) return this._closing
|
|
196
325
|
this._closing = this._close()
|
|
197
|
-
this._closing.catch(
|
|
326
|
+
this._closing.catch(safetyCatch)
|
|
198
327
|
return this._closing
|
|
199
328
|
}
|
|
329
|
+
}
|
|
200
330
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
331
|
+
function sign (keyPair, message) {
|
|
332
|
+
if (!keyPair.secretKey) throw new Error('Invalid key pair')
|
|
333
|
+
return crypto.sign(message, keyPair.secretKey)
|
|
204
334
|
}
|
|
205
335
|
|
|
206
336
|
function validateGetOptions (opts) {
|
|
207
|
-
if (
|
|
337
|
+
if (b4a.isBuffer(opts)) return { key: opts, publicKey: opts }
|
|
208
338
|
if (opts.key) {
|
|
209
339
|
opts.publicKey = opts.key
|
|
210
340
|
}
|
|
@@ -214,23 +344,30 @@ function validateGetOptions (opts) {
|
|
|
214
344
|
}
|
|
215
345
|
if (opts.name && typeof opts.name !== 'string') throw new Error('name option must be a String')
|
|
216
346
|
if (opts.name && opts.secretKey) throw new Error('Cannot provide both a name and a secret key')
|
|
217
|
-
if (opts.publicKey && !
|
|
218
|
-
if (opts.secretKey && !
|
|
219
|
-
if (opts.
|
|
220
|
-
if (!opts.name && !opts.publicKey) throw new Error('Must provide either a name or a publicKey')
|
|
347
|
+
if (opts.publicKey && !b4a.isBuffer(opts.publicKey)) throw new Error('publicKey option must be a Buffer or Uint8Array')
|
|
348
|
+
if (opts.secretKey && !b4a.isBuffer(opts.secretKey)) throw new Error('secretKey option must be a Buffer or Uint8Array')
|
|
349
|
+
if (!opts._discoveryKey && (!opts.name && !opts.publicKey)) throw new Error('Must provide either a name or a publicKey')
|
|
221
350
|
return opts
|
|
222
351
|
}
|
|
223
352
|
|
|
224
|
-
function generateNamespace (
|
|
225
|
-
if (!
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
353
|
+
function generateNamespace (namespace, name) {
|
|
354
|
+
if (!b4a.isBuffer(name)) name = b4a.from(name)
|
|
355
|
+
const out = b4a.allocUnsafe(32)
|
|
356
|
+
sodium.crypto_generichash_batch(out, [namespace, name])
|
|
357
|
+
return out
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function deriveSeed (primaryKey, namespace, name) {
|
|
361
|
+
if (!b4a.isBuffer(name)) name = b4a.from(name)
|
|
362
|
+
const out = b4a.alloc(32)
|
|
363
|
+
sodium.crypto_generichash_batch(out, [NS, namespace, name], primaryKey)
|
|
229
364
|
return out
|
|
230
365
|
}
|
|
231
366
|
|
|
367
|
+
function defaultCache () {
|
|
368
|
+
return new Xache({ maxSize: 65536, maxAge: 0 })
|
|
369
|
+
}
|
|
370
|
+
|
|
232
371
|
function isStream (s) {
|
|
233
372
|
return typeof s === 'object' && s && typeof s.pipe === 'function'
|
|
234
373
|
}
|
|
235
|
-
|
|
236
|
-
function noop () {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "corestore",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.3",
|
|
4
4
|
"description": "A Hypercore factory that simplifies managing collections of cores.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -20,17 +20,16 @@
|
|
|
20
20
|
},
|
|
21
21
|
"homepage": "https://github.com/hypercore-protocol/corestore#readme",
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"brittle": "^
|
|
24
|
-
"random-access-
|
|
25
|
-
"
|
|
26
|
-
"standardx": "^7.0.0",
|
|
27
|
-
"tmp-promise": "^3.0.2"
|
|
23
|
+
"brittle": "^3.0.0",
|
|
24
|
+
"random-access-memory": "^5.0.1",
|
|
25
|
+
"standardx": "^7.0.0"
|
|
28
26
|
},
|
|
29
27
|
"dependencies": {
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"hypercore": "
|
|
33
|
-
"
|
|
34
|
-
"sodium-universal": "^3.0.4"
|
|
28
|
+
"b4a": "^1.3.1",
|
|
29
|
+
"hypercore": "^10.2.0",
|
|
30
|
+
"hypercore-crypto": "^3.2.1",
|
|
31
|
+
"safety-catch": "^1.0.1",
|
|
32
|
+
"sodium-universal": "^3.0.4",
|
|
33
|
+
"xache": "^1.1.0"
|
|
35
34
|
}
|
|
36
35
|
}
|
package/test/all.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
const test = require('brittle')
|
|
2
2
|
const crypto = require('hypercore-crypto')
|
|
3
3
|
const ram = require('random-access-memory')
|
|
4
|
-
const
|
|
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
|
|
|
@@ -77,6 +80,44 @@ test('basic replication', async function (t) {
|
|
|
77
80
|
t.alike(await core4.get(0), Buffer.from('world'))
|
|
78
81
|
})
|
|
79
82
|
|
|
83
|
+
test('replicating cores created after replication begins', async function (t) {
|
|
84
|
+
const store1 = new Corestore(ram)
|
|
85
|
+
const store2 = new Corestore(ram)
|
|
86
|
+
|
|
87
|
+
const s = store1.replicate(true, { live: true })
|
|
88
|
+
s.pipe(store2.replicate(false, { live: true })).pipe(s)
|
|
89
|
+
|
|
90
|
+
const core1 = store1.get({ name: 'core-1' })
|
|
91
|
+
const core2 = store1.get({ name: 'core-2' })
|
|
92
|
+
await core1.append('hello')
|
|
93
|
+
await core2.append('world')
|
|
94
|
+
|
|
95
|
+
const core3 = store2.get({ key: core1.key })
|
|
96
|
+
const core4 = store2.get({ key: core2.key })
|
|
97
|
+
|
|
98
|
+
t.alike(await core3.get(0), Buffer.from('hello'))
|
|
99
|
+
t.alike(await core4.get(0), Buffer.from('world'))
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('replicating cores using discovery key hook', async function (t) {
|
|
103
|
+
const dir = tmpdir()
|
|
104
|
+
let store1 = new Corestore(dir)
|
|
105
|
+
const store2 = new Corestore(ram)
|
|
106
|
+
|
|
107
|
+
const core = store1.get({ name: 'main' })
|
|
108
|
+
await core.append('hello')
|
|
109
|
+
const key = core.key
|
|
110
|
+
|
|
111
|
+
await store1.close()
|
|
112
|
+
store1 = new Corestore(dir)
|
|
113
|
+
|
|
114
|
+
const s = store1.replicate(true, { live: true })
|
|
115
|
+
s.pipe(store2.replicate(false, { live: true })).pipe(s)
|
|
116
|
+
|
|
117
|
+
const core2 = store2.get(key)
|
|
118
|
+
t.alike(await core2.get(0), Buffer.from('hello'))
|
|
119
|
+
})
|
|
120
|
+
|
|
80
121
|
test('nested namespaces', async function (t) {
|
|
81
122
|
const store = new Corestore(ram)
|
|
82
123
|
const ns1a = store.namespace('ns1').namespace('a')
|
|
@@ -102,9 +143,9 @@ test('core uncached when all sessions close', async function (t) {
|
|
|
102
143
|
})
|
|
103
144
|
|
|
104
145
|
test('writable core loaded from name userData', async function (t) {
|
|
105
|
-
const dir =
|
|
146
|
+
const dir = tmpdir()
|
|
106
147
|
|
|
107
|
-
let store = new Corestore(dir
|
|
148
|
+
let store = new Corestore(dir)
|
|
108
149
|
let core = store.get({ name: 'main' })
|
|
109
150
|
await core.ready()
|
|
110
151
|
const key = core.key
|
|
@@ -114,7 +155,7 @@ test('writable core loaded from name userData', async function (t) {
|
|
|
114
155
|
t.is(core.length, 1)
|
|
115
156
|
|
|
116
157
|
await store.close()
|
|
117
|
-
store = new Corestore(dir
|
|
158
|
+
store = new Corestore(dir)
|
|
118
159
|
core = store.get(key)
|
|
119
160
|
await core.ready()
|
|
120
161
|
|
|
@@ -123,23 +164,171 @@ test('writable core loaded from name userData', async function (t) {
|
|
|
123
164
|
t.is(core.length, 2)
|
|
124
165
|
t.alike(await core.get(0), Buffer.from('hello'))
|
|
125
166
|
t.alike(await core.get(1), Buffer.from('world'))
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('writable core loaded from name and namespace userData', async function (t) {
|
|
170
|
+
const dir = tmpdir()
|
|
171
|
+
|
|
172
|
+
let store = new Corestore(dir)
|
|
173
|
+
let core = store.namespace('ns1').get({ name: 'main' })
|
|
174
|
+
await core.ready()
|
|
175
|
+
const key = core.key
|
|
176
|
+
|
|
177
|
+
t.ok(core.writable)
|
|
178
|
+
await core.append('hello')
|
|
179
|
+
t.is(core.length, 1)
|
|
180
|
+
|
|
181
|
+
await store.close()
|
|
182
|
+
store = new Corestore(dir)
|
|
183
|
+
core = store.get(key)
|
|
184
|
+
await core.ready()
|
|
126
185
|
|
|
127
|
-
|
|
186
|
+
t.ok(core.writable)
|
|
187
|
+
await core.append('world')
|
|
188
|
+
t.is(core.length, 2)
|
|
189
|
+
t.alike(await core.get(0), Buffer.from('hello'))
|
|
190
|
+
t.alike(await core.get(1), Buffer.from('world'))
|
|
128
191
|
})
|
|
129
192
|
|
|
130
193
|
test('storage locking', async function (t) {
|
|
131
|
-
const dir =
|
|
194
|
+
const dir = tmpdir()
|
|
132
195
|
|
|
133
|
-
const store1 = new Corestore(dir
|
|
196
|
+
const store1 = new Corestore(dir)
|
|
134
197
|
await store1.ready()
|
|
135
198
|
|
|
136
|
-
const store2 = new Corestore(dir
|
|
199
|
+
const store2 = new Corestore(dir)
|
|
137
200
|
try {
|
|
138
201
|
await store2.ready()
|
|
139
202
|
t.fail('dir should have been locked')
|
|
140
203
|
} catch {
|
|
141
204
|
t.pass('dir was locked')
|
|
142
205
|
}
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
test('closing a namespace does not close cores', async function (t) {
|
|
209
|
+
const store = new Corestore(ram)
|
|
210
|
+
const ns1 = store.namespace('ns1')
|
|
211
|
+
const core1 = ns1.get({ name: 'core-1' })
|
|
212
|
+
const core2 = ns1.get({ name: 'core-2' })
|
|
213
|
+
await Promise.all([core1.ready(), core2.ready()])
|
|
214
|
+
|
|
215
|
+
await ns1.close()
|
|
143
216
|
|
|
144
|
-
|
|
217
|
+
t.is(store.cores.size, 2)
|
|
218
|
+
t.not(core1.closed)
|
|
219
|
+
t.not(core1.closed)
|
|
220
|
+
|
|
221
|
+
await store.close()
|
|
222
|
+
|
|
223
|
+
t.is(store.cores.size, 0)
|
|
224
|
+
t.ok(core1.closed)
|
|
225
|
+
t.ok(core2.closed)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
test('findingPeers', async function (t) {
|
|
229
|
+
t.plan(6)
|
|
230
|
+
|
|
231
|
+
const store = new Corestore(ram)
|
|
232
|
+
|
|
233
|
+
const ns1 = store.namespace('ns1')
|
|
234
|
+
const ns2 = store.namespace('ns2')
|
|
235
|
+
|
|
236
|
+
const a = ns1.get(Buffer.alloc(32).fill('a'))
|
|
237
|
+
const b = ns2.get(Buffer.alloc(32).fill('b'))
|
|
238
|
+
|
|
239
|
+
const done = ns1.findingPeers()
|
|
240
|
+
|
|
241
|
+
let aUpdated = false
|
|
242
|
+
let bUpdated = false
|
|
243
|
+
let cUpdated = false
|
|
244
|
+
|
|
245
|
+
const c = ns1.get(Buffer.alloc(32).fill('c'))
|
|
246
|
+
|
|
247
|
+
a.update().then(function (bool) {
|
|
248
|
+
aUpdated = true
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
b.update().then(function (bool) {
|
|
252
|
+
bUpdated = true
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
c.update().then(function (bool) {
|
|
256
|
+
cUpdated = true
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
await new Promise(resolve => setImmediate(resolve))
|
|
260
|
+
|
|
261
|
+
t.is(aUpdated, false)
|
|
262
|
+
t.is(bUpdated, true)
|
|
263
|
+
t.is(cUpdated, false)
|
|
264
|
+
|
|
265
|
+
done()
|
|
266
|
+
|
|
267
|
+
await new Promise(resolve => setImmediate(resolve))
|
|
268
|
+
|
|
269
|
+
t.is(aUpdated, true)
|
|
270
|
+
t.is(bUpdated, true)
|
|
271
|
+
t.is(cUpdated, true)
|
|
145
272
|
})
|
|
273
|
+
|
|
274
|
+
test('different primary keys yield different keypairs', async function (t) {
|
|
275
|
+
const pk1 = randomBytes(32)
|
|
276
|
+
const pk2 = randomBytes(32)
|
|
277
|
+
t.unlike(pk1, pk2)
|
|
278
|
+
|
|
279
|
+
const store1 = new Corestore(ram, { primaryKey: pk1 })
|
|
280
|
+
const store2 = new Corestore(ram, { primaryKey: pk2 })
|
|
281
|
+
|
|
282
|
+
const kp1 = await store1.createKeyPair('hello')
|
|
283
|
+
const kp2 = await store2.createKeyPair('hello')
|
|
284
|
+
|
|
285
|
+
t.unlike(kp1.publicKey, kp2.publicKey)
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
test('keypair auth sign', async function (t) {
|
|
289
|
+
const store = new Corestore(ram)
|
|
290
|
+
const keyPair = await store.createKeyPair('foo')
|
|
291
|
+
const message = b4a.from('hello world')
|
|
292
|
+
|
|
293
|
+
const sig = keyPair.auth.sign(message)
|
|
294
|
+
|
|
295
|
+
t.is(sig.length, 64)
|
|
296
|
+
t.ok(crypto.verify(message, sig, keyPair.publicKey))
|
|
297
|
+
t.absent(crypto.verify(message, b4a.alloc(64), keyPair.publicKey))
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
test('keypair auth verify', async function (t) {
|
|
301
|
+
const store = new Corestore(ram)
|
|
302
|
+
const keyPair = await store.createKeyPair('foo')
|
|
303
|
+
const message = b4a.from('hello world')
|
|
304
|
+
|
|
305
|
+
const sig = crypto.sign(message, keyPair.secretKey)
|
|
306
|
+
|
|
307
|
+
t.is(sig.length, 64)
|
|
308
|
+
t.ok(keyPair.auth.verify(message, sig))
|
|
309
|
+
t.absent(keyPair.auth.verify(message, b4a.alloc(64)))
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
test('core caching after reopen regression', async function (t) {
|
|
313
|
+
const store = new Corestore(ram)
|
|
314
|
+
const core = store.get({ name: 'test-core' })
|
|
315
|
+
await core.ready()
|
|
316
|
+
|
|
317
|
+
core.close()
|
|
318
|
+
await core.opening
|
|
319
|
+
|
|
320
|
+
const core2 = store.get({ name: 'test-core' })
|
|
321
|
+
await core2.ready()
|
|
322
|
+
|
|
323
|
+
t.pass('did not infinite loop')
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
function tmpdir () {
|
|
327
|
+
return path.join(os.tmpdir(), 'corestore-' + Math.random().toString(16).slice(2))
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function randomBytes (n) {
|
|
331
|
+
const buf = b4a.allocUnsafe(n)
|
|
332
|
+
sodium.randombytes_buf(buf)
|
|
333
|
+
return buf
|
|
334
|
+
}
|
package/test/cache.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const test = require('brittle')
|
|
2
|
+
const RAM = require('random-access-memory')
|
|
3
|
+
|
|
4
|
+
const Corestore = require('..')
|
|
5
|
+
|
|
6
|
+
test('core cache', async function (t) {
|
|
7
|
+
const store = new Corestore(RAM, { cache: true })
|
|
8
|
+
|
|
9
|
+
const core = store.get({ name: 'core' })
|
|
10
|
+
await core.append(['a', 'b', 'c'])
|
|
11
|
+
|
|
12
|
+
const p = core.get(0)
|
|
13
|
+
const q = core.get(0)
|
|
14
|
+
|
|
15
|
+
t.is(await p, await q)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('clear cache on truncate', async function (t) {
|
|
19
|
+
const store = new Corestore(RAM, { cache: true })
|
|
20
|
+
|
|
21
|
+
const core = store.get({ name: 'core' })
|
|
22
|
+
await core.append(['a', 'b', 'c'])
|
|
23
|
+
|
|
24
|
+
const p = core.get(0)
|
|
25
|
+
|
|
26
|
+
await core.truncate(0)
|
|
27
|
+
await core.append('d')
|
|
28
|
+
|
|
29
|
+
const q = core.get(0)
|
|
30
|
+
|
|
31
|
+
t.alike(await p, Buffer.from('a'))
|
|
32
|
+
t.alike(await q, Buffer.from('d'))
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('core cache on namespace', async function (t) {
|
|
36
|
+
const store = new Corestore(RAM, { cache: true })
|
|
37
|
+
const ns1 = store.namespace('test-namespace-1')
|
|
38
|
+
|
|
39
|
+
const c1 = store.get({ name: 'test-core' })
|
|
40
|
+
const c2 = ns1.get({ name: 'test-core' })
|
|
41
|
+
|
|
42
|
+
await Promise.all([c1.ready(), c2.ready()])
|
|
43
|
+
|
|
44
|
+
t.ok(c1.cache)
|
|
45
|
+
t.ok(c2.cache)
|
|
46
|
+
})
|
package/lib/keys.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
// TODO: Extract this into a standalone module
|
|
2
|
-
|
|
3
|
-
const sodium = require('sodium-universal')
|
|
4
|
-
const blake2b = require('blake2b-universal')
|
|
5
|
-
|
|
6
|
-
const DEFAULT_TOKEN = Buffer.alloc(0)
|
|
7
|
-
const NAMESPACE = Buffer.from('@hyperspace/key-manager')
|
|
8
|
-
|
|
9
|
-
module.exports = class KeyManager {
|
|
10
|
-
constructor (storage, profile, opts = {}) {
|
|
11
|
-
this.storage = storage
|
|
12
|
-
this.profile = profile
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
_sign (keyPair, message) {
|
|
16
|
-
if (!keyPair._secretKey) throw new Error('Invalid key pair')
|
|
17
|
-
const signature = Buffer.allocUnsafe(sodium.crypto_sign_BYTES)
|
|
18
|
-
sodium.crypto_sign_detached(signature, message, keyPair._secretKey)
|
|
19
|
-
return signature
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
createSecret (name, token) {
|
|
23
|
-
return deriveSeed(this.profile, token, name)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
createHypercoreKeyPair (name, token) {
|
|
27
|
-
const keyPair = {
|
|
28
|
-
publicKey: Buffer.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES),
|
|
29
|
-
_secretKey: Buffer.alloc(sodium.crypto_sign_SECRETKEYBYTES),
|
|
30
|
-
sign: (msg) => this._sign(keyPair, msg)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair._secretKey, this.createSecret(name, token))
|
|
34
|
-
|
|
35
|
-
return keyPair
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
createNetworkIdentity (name, token) {
|
|
39
|
-
const keyPair = {
|
|
40
|
-
publicKey: Buffer.alloc(32),
|
|
41
|
-
secretKey: Buffer.alloc(64)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair.secretKey, this.createSecret(name, token))
|
|
45
|
-
|
|
46
|
-
return keyPair
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
close () {
|
|
50
|
-
return new Promise((resolve, reject) => {
|
|
51
|
-
this.storage.close(err => {
|
|
52
|
-
if (err) return reject(err)
|
|
53
|
-
return resolve()
|
|
54
|
-
})
|
|
55
|
-
})
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
static createToken () {
|
|
59
|
-
return randomBytes(32)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
static async fromStorage (storage, opts = {}) {
|
|
63
|
-
const profileStorage = storage(opts.name || 'default')
|
|
64
|
-
|
|
65
|
-
const profile = await new Promise((resolve, reject) => {
|
|
66
|
-
profileStorage.stat((err, st) => {
|
|
67
|
-
if (err && err.code !== 'ENOENT') return reject(err)
|
|
68
|
-
if (err || st.size < 32 || opts.overwrite) {
|
|
69
|
-
const key = randomBytes(32)
|
|
70
|
-
return profileStorage.write(0, key, err => {
|
|
71
|
-
if (err) return reject(err)
|
|
72
|
-
return resolve(key)
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
profileStorage.read(0, 32, (err, key) => {
|
|
76
|
-
if (err) return reject(err)
|
|
77
|
-
return resolve(key)
|
|
78
|
-
})
|
|
79
|
-
})
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
return new this(profileStorage, profile, opts)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function deriveSeed (profile, token, name, output) {
|
|
87
|
-
if (token && token.length < 32) throw new Error('Token must be a Buffer with length >= 32')
|
|
88
|
-
if (!name || typeof name !== 'string') throw new Error('name must be a String')
|
|
89
|
-
if (!output) output = Buffer.alloc(32)
|
|
90
|
-
|
|
91
|
-
blake2b.batch(output, [
|
|
92
|
-
NAMESPACE,
|
|
93
|
-
token || DEFAULT_TOKEN,
|
|
94
|
-
Buffer.from(Buffer.byteLength(name, 'ascii') + '\n' + name, 'ascii')
|
|
95
|
-
], profile)
|
|
96
|
-
|
|
97
|
-
return output
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function randomBytes (n) {
|
|
101
|
-
const buf = Buffer.allocUnsafe(n)
|
|
102
|
-
sodium.randombytes_buf(buf)
|
|
103
|
-
return buf
|
|
104
|
-
}
|
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
|
-
})
|