corestore 6.0.0-beta1 → 6.0.1-alpha.11
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 +4 -4
- package/README.md +59 -2
- package/index.js +215 -81
- package/lib/keys.js +105 -0
- package/package.json +17 -24
- package/test/all.js +142 -237
- package/test/helpers/index.js +2 -7
- package/test/keys.js +65 -0
- package/lib/buffer-file.js +0 -26
- package/lib/db.js +0 -160
- package/lib/errors.js +0 -44
- package/lib/loader.js +0 -287
- package/lib/pending-file.js +0 -37
- package/lib/replicator.js +0 -66
|
@@ -3,16 +3,16 @@ name: Test on Node.js
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
5
|
branches:
|
|
6
|
-
-
|
|
6
|
+
- main
|
|
7
7
|
pull_request:
|
|
8
8
|
branches:
|
|
9
|
-
-
|
|
9
|
+
- main
|
|
10
10
|
jobs:
|
|
11
11
|
build:
|
|
12
12
|
strategy:
|
|
13
13
|
matrix:
|
|
14
|
-
node-version: [12.x, 14.x]
|
|
15
|
-
os: [ubuntu-
|
|
14
|
+
node-version: [12.x, 14.x, 16.x]
|
|
15
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
16
16
|
runs-on: ${{ matrix.os }}
|
|
17
17
|
steps:
|
|
18
18
|
- uses: actions/checkout@v2
|
package/README.md
CHANGED
|
@@ -1,2 +1,59 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
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,256 @@
|
|
|
1
|
-
const {
|
|
2
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
18
|
-
this.
|
|
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.
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
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.
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
42
|
+
async _generateKeys (opts) {
|
|
43
|
+
if (opts._discoveryKey) {
|
|
44
|
+
return {
|
|
45
|
+
keyPair: null,
|
|
46
|
+
sign: 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
|
+
discoveryKey: crypto.discoveryKey(opts.publicKey)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const { publicKey, sign } = await this.keys.createHypercoreKeyPair(opts.name, this._namespace)
|
|
61
|
+
return {
|
|
62
|
+
keyPair: {
|
|
63
|
+
publicKey,
|
|
64
|
+
secretKey: null
|
|
65
|
+
},
|
|
66
|
+
sign,
|
|
67
|
+
discoveryKey: crypto.discoveryKey(publicKey)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
35
70
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
71
|
+
_getPrereadyUserData (core, key) {
|
|
72
|
+
for (const { key: savedKey, value } of core.core.header.userData) {
|
|
73
|
+
if (key === savedKey) return value
|
|
74
|
+
}
|
|
75
|
+
return null
|
|
39
76
|
}
|
|
40
77
|
|
|
41
|
-
async
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
78
|
+
async _preready (core) {
|
|
79
|
+
const name = this._getPrereadyUserData(core, USERDATA_NAME_KEY)
|
|
80
|
+
if (!name) return
|
|
81
|
+
|
|
82
|
+
const namespace = this._getPrereadyUserData(core, USERDATA_NAMESPACE_KEY)
|
|
83
|
+
const { publicKey, sign } = await this.keys.createHypercoreKeyPair(b4a.toString(name), namespace)
|
|
84
|
+
if (!b4a.equals(publicKey, core.key)) throw new Error('Stored core key does not match the provided name')
|
|
85
|
+
|
|
86
|
+
// TODO: Should Hypercore expose a helper for this, or should preready return keypair/sign?
|
|
87
|
+
core.sign = sign
|
|
88
|
+
core.key = publicKey
|
|
89
|
+
core.writable = true
|
|
45
90
|
}
|
|
46
91
|
|
|
47
|
-
|
|
92
|
+
async _preload (opts) {
|
|
93
|
+
await this.ready()
|
|
94
|
+
|
|
95
|
+
const { discoveryKey, keyPair, sign } = await this._generateKeys(opts)
|
|
96
|
+
const id = b4a.toString(discoveryKey, 'hex')
|
|
48
97
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (!
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
98
|
+
while (this.cores.has(id)) {
|
|
99
|
+
const existing = this.cores.get(id)
|
|
100
|
+
if (existing.opened && !existing.closing) return { from: existing, keyPair, sign }
|
|
101
|
+
if (!existing.opened) {
|
|
102
|
+
await existing.ready().catch(safetyCatch)
|
|
103
|
+
} else if (existing.closing) {
|
|
104
|
+
await existing.close()
|
|
55
105
|
}
|
|
56
|
-
} else {
|
|
57
|
-
opts = { key: Buffer.from(opts, 'hex') }
|
|
58
106
|
}
|
|
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
107
|
|
|
65
|
-
|
|
108
|
+
const userData = {}
|
|
109
|
+
if (opts.name) {
|
|
110
|
+
userData[USERDATA_NAME_KEY] = b4a.from(opts.name)
|
|
111
|
+
userData[USERDATA_NAMESPACE_KEY] = this._namespace
|
|
112
|
+
}
|
|
66
113
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
114
|
+
// No more async ticks allowed after this point -- necessary for caching
|
|
115
|
+
|
|
116
|
+
const storageRoot = [CORES_DIR, id.slice(0, 2), id.slice(2, 4), id].join('/')
|
|
117
|
+
const core = new Hypercore(p => this.storage(storageRoot + '/' + p), {
|
|
118
|
+
_preready: this._preready.bind(this),
|
|
119
|
+
autoClose: true,
|
|
120
|
+
encryptionKey: opts.encryptionKey || null,
|
|
121
|
+
userData,
|
|
122
|
+
sign: null,
|
|
123
|
+
createIfMissing: !opts._discoveryKey,
|
|
124
|
+
keyPair: keyPair && keyPair.publicKey
|
|
125
|
+
? {
|
|
126
|
+
publicKey: keyPair.publicKey,
|
|
127
|
+
secretKey: null
|
|
128
|
+
}
|
|
129
|
+
: null
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
this.cores.set(id, core)
|
|
133
|
+
core.ready().then(() => {
|
|
134
|
+
for (const { stream } of this._replicationStreams) {
|
|
135
|
+
const sessions = this._streamSessions.get(stream)
|
|
136
|
+
const session = core.session()
|
|
137
|
+
sessions.push(session)
|
|
138
|
+
core.replicate(stream)
|
|
139
|
+
}
|
|
140
|
+
}, () => {
|
|
141
|
+
this.cores.delete(id)
|
|
142
|
+
})
|
|
143
|
+
core.once('close', () => {
|
|
144
|
+
this.cores.delete(id)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
return { from: core, keyPair, sign }
|
|
71
148
|
}
|
|
72
149
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
_loader: this._loader,
|
|
80
|
-
_replicator: this._replicator
|
|
150
|
+
get (opts = {}) {
|
|
151
|
+
opts = validateGetOptions(opts)
|
|
152
|
+
const core = new Hypercore(null, {
|
|
153
|
+
...opts,
|
|
154
|
+
name: null,
|
|
155
|
+
preload: () => this._preload(opts)
|
|
81
156
|
})
|
|
157
|
+
return core
|
|
82
158
|
}
|
|
83
159
|
|
|
84
160
|
replicate (isInitiator, opts) {
|
|
85
|
-
|
|
161
|
+
const isExternal = isStream(isInitiator) || !!(opts && opts.stream)
|
|
162
|
+
const stream = Hypercore.createProtocolStream(isInitiator, {
|
|
163
|
+
...opts,
|
|
164
|
+
ondiscoverykey: discoveryKey => {
|
|
165
|
+
const core = this.get({ _discoveryKey: discoveryKey })
|
|
166
|
+
return core.ready().catch(safetyCatch)
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
const sessions = []
|
|
171
|
+
for (const core of this.cores.values()) {
|
|
172
|
+
if (!core.opened) continue // If the core is not opened, it will be replicated in preload.
|
|
173
|
+
const session = core.session()
|
|
174
|
+
sessions.push(session)
|
|
175
|
+
core.replicate(stream)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const streamRecord = { stream, isExternal }
|
|
179
|
+
this._replicationStreams.push(streamRecord)
|
|
180
|
+
this._streamSessions.set(stream, sessions)
|
|
181
|
+
|
|
182
|
+
stream.once('close', () => {
|
|
183
|
+
this._replicationStreams.splice(this._replicationStreams.indexOf(streamRecord), 1)
|
|
184
|
+
this._streamSessions.delete(stream)
|
|
185
|
+
Promise.all(sessions.map(s => s.close())).catch(safetyCatch)
|
|
186
|
+
})
|
|
187
|
+
return stream
|
|
86
188
|
}
|
|
87
189
|
|
|
88
|
-
|
|
190
|
+
namespace (name) {
|
|
191
|
+
if (!b4a.isBuffer(name)) name = b4a.from(name)
|
|
192
|
+
return new Corestore(this.storage, {
|
|
193
|
+
_namespace: generateNamespace(this._namespace, name),
|
|
194
|
+
_opening: this._opening,
|
|
195
|
+
_cores: this.cores,
|
|
196
|
+
_streams: this._replicationStreams,
|
|
197
|
+
_streamSessions: this._streamSessions,
|
|
198
|
+
keys: this._opening.then(() => this.keys)
|
|
199
|
+
})
|
|
200
|
+
}
|
|
89
201
|
|
|
90
|
-
async
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
202
|
+
async _close () {
|
|
203
|
+
await this._opening
|
|
204
|
+
if (!this._namespace.equals(DEFAULT_NAMESPACE)) return // namespaces should not release resources on close
|
|
205
|
+
const closePromises = []
|
|
206
|
+
for (const core of this.cores.values()) {
|
|
207
|
+
closePromises.push(core.close())
|
|
208
|
+
}
|
|
209
|
+
await Promise.allSettled(closePromises)
|
|
210
|
+
for (const { stream, isExternal } of this._replicationStreams) {
|
|
211
|
+
// Only close streams that were created by the Corestore
|
|
212
|
+
if (!isExternal) stream.destroy()
|
|
96
213
|
}
|
|
214
|
+
await this.keys.close()
|
|
97
215
|
}
|
|
98
216
|
|
|
99
|
-
|
|
100
|
-
return this.
|
|
217
|
+
close () {
|
|
218
|
+
if (this._closing) return this._closing
|
|
219
|
+
this._closing = this._close()
|
|
220
|
+
this._closing.catch(safetyCatch)
|
|
221
|
+
return this._closing
|
|
101
222
|
}
|
|
102
223
|
|
|
103
|
-
static
|
|
104
|
-
|
|
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
|
|
224
|
+
static createToken () {
|
|
225
|
+
return KeyManager.createToken()
|
|
111
226
|
}
|
|
112
227
|
}
|
|
113
228
|
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
229
|
+
function validateGetOptions (opts) {
|
|
230
|
+
if (b4a.isBuffer(opts)) return { key: opts, publicKey: opts }
|
|
231
|
+
if (opts.key) {
|
|
232
|
+
opts.publicKey = opts.key
|
|
233
|
+
}
|
|
234
|
+
if (opts.keyPair) {
|
|
235
|
+
opts.publicKey = opts.keyPair.publicKey
|
|
236
|
+
opts.secretKey = opts.keyPair.secretKey
|
|
121
237
|
}
|
|
238
|
+
if (opts.name && typeof opts.name !== 'string') throw new Error('name option must be a String')
|
|
239
|
+
if (opts.name && opts.secretKey) throw new Error('Cannot provide both a name and a secret key')
|
|
240
|
+
if (opts.publicKey && !b4a.isBuffer(opts.publicKey)) throw new Error('publicKey option must be a Buffer or Uint8Array')
|
|
241
|
+
if (opts.secretKey && !b4a.isBuffer(opts.secretKey)) throw new Error('secretKey option must be a Buffer or Uint8Array')
|
|
242
|
+
if (!opts._discoveryKey && (!opts.name && !opts.publicKey)) throw new Error('Must provide either a name or a publicKey')
|
|
243
|
+
return opts
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function generateNamespace (first, second) {
|
|
247
|
+
if (!b4a.isBuffer(first)) first = b4a.from(first)
|
|
248
|
+
if (second && !b4a.isBuffer(second)) second = b4a.from(second)
|
|
249
|
+
const out = b4a.allocUnsafe(32)
|
|
250
|
+
sodium.crypto_generichash(out, second ? b4a.concat([first, second]) : first)
|
|
251
|
+
return out
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function isStream (s) {
|
|
255
|
+
return typeof s === 'object' && s && typeof s.pipe === 'function'
|
|
122
256
|
}
|
package/lib/keys.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
sign: (msg) => this._sign(keyPair, msg)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair._secretKey, this.createSecret(name, token))
|
|
35
|
+
|
|
36
|
+
return keyPair
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
createNetworkIdentity (name, token) {
|
|
40
|
+
const keyPair = {
|
|
41
|
+
publicKey: b4a.alloc(32),
|
|
42
|
+
secretKey: b4a.alloc(64)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair.secretKey, this.createSecret(name, token))
|
|
46
|
+
|
|
47
|
+
return keyPair
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
close () {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
this.storage.close(err => {
|
|
53
|
+
if (err) return reject(err)
|
|
54
|
+
return resolve()
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static createToken () {
|
|
60
|
+
return randomBytes(32)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static async fromStorage (storage, opts = {}) {
|
|
64
|
+
const profileStorage = storage(opts.name || 'default')
|
|
65
|
+
|
|
66
|
+
const profile = await new Promise((resolve, reject) => {
|
|
67
|
+
profileStorage.stat((err, st) => {
|
|
68
|
+
if (err && err.code !== 'ENOENT') return reject(err)
|
|
69
|
+
if (err || st.size < 32 || opts.overwrite) {
|
|
70
|
+
const key = randomBytes(32)
|
|
71
|
+
return profileStorage.write(0, key, err => {
|
|
72
|
+
if (err) return reject(err)
|
|
73
|
+
return resolve(key)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
profileStorage.read(0, 32, (err, key) => {
|
|
77
|
+
if (err) return reject(err)
|
|
78
|
+
return resolve(key)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
return new this(profileStorage, profile, opts)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function deriveSeed (profile, token, name, output) {
|
|
88
|
+
if (token && token.length < 32) throw new Error('Token must be a Buffer with length >= 32')
|
|
89
|
+
if (!name || typeof name !== 'string') throw new Error('name must be a String')
|
|
90
|
+
if (!output) output = b4a.alloc(32)
|
|
91
|
+
|
|
92
|
+
blake2b.batch(output, [
|
|
93
|
+
NAMESPACE,
|
|
94
|
+
token || DEFAULT_TOKEN,
|
|
95
|
+
b4a.from(b4a.byteLength(name, 'ascii') + '\n' + name, 'ascii')
|
|
96
|
+
], profile)
|
|
97
|
+
|
|
98
|
+
return output
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function randomBytes (n) {
|
|
102
|
+
const buf = b4a.allocUnsafe(n)
|
|
103
|
+
sodium.randombytes_buf(buf)
|
|
104
|
+
return buf
|
|
105
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "corestore",
|
|
3
|
-
"version": "6.0.
|
|
4
|
-
"description": "A Hypercore factory that
|
|
3
|
+
"version": "6.0.1-alpha.11",
|
|
4
|
+
"description": "A Hypercore factory that simplifies managing collections of cores.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "standard &&
|
|
7
|
+
"test": "standard && brittle test/*.js"
|
|
8
8
|
},
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/
|
|
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/
|
|
19
|
+
"url": "https://github.com/hypercore-protocol/corestore/issues"
|
|
20
20
|
},
|
|
21
|
-
"homepage": "https://github.com/
|
|
21
|
+
"homepage": "https://github.com/hypercore-protocol/corestore#readme",
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"random-access-memory": "^3.1.
|
|
26
|
-
"
|
|
27
|
-
"
|
|
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
|
-
"
|
|
31
|
-
"
|
|
30
|
+
"b4a": "^1.3.1",
|
|
31
|
+
"blake2b-universal": "^1.0.1",
|
|
32
32
|
"derive-key": "^1.0.1",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
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
|
}
|