hypercore 10.0.0-alpha.1 → 10.0.0-alpha.13
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 +1 -2
- package/README.md +30 -1
- package/UPGRADE.md +6 -0
- package/__snapshots__/test/storage.js.snapshot.cjs +15 -0
- package/index.js +230 -105
- package/lib/bitfield.js +3 -2
- package/lib/block-encryption.js +68 -0
- package/lib/block-store.js +3 -1
- package/lib/core.js +3 -1
- package/lib/merkle-tree.js +43 -29
- package/lib/oplog.js +4 -3
- package/lib/protocol.js +9 -6
- package/lib/replicator.js +60 -15
- package/lib/streams.js +39 -0
- package/package.json +7 -5
- package/test/basic.js +12 -0
- package/test/encryption.js +123 -0
- package/test/replicate.js +76 -0
- package/test/sessions.js +11 -1
- package/test/storage.js +31 -0
- package/test/streams.js +55 -0
package/index.js
CHANGED
|
@@ -3,6 +3,8 @@ const raf = require('random-access-file')
|
|
|
3
3
|
const isOptions = require('is-options')
|
|
4
4
|
const hypercoreCrypto = require('hypercore-crypto')
|
|
5
5
|
const c = require('compact-encoding')
|
|
6
|
+
const b4a = require('b4a')
|
|
7
|
+
const Xache = require('xache')
|
|
6
8
|
const NoiseSecretStream = require('@hyperswarm/secret-stream')
|
|
7
9
|
const codecs = require('codecs')
|
|
8
10
|
|
|
@@ -11,6 +13,8 @@ const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
|
|
|
11
13
|
const Replicator = require('./lib/replicator')
|
|
12
14
|
const Extensions = require('./lib/extensions')
|
|
13
15
|
const Core = require('./lib/core')
|
|
16
|
+
const BlockEncryption = require('./lib/block-encryption')
|
|
17
|
+
const { ReadStream } = require('./lib/streams')
|
|
14
18
|
|
|
15
19
|
const promises = Symbol.for('hypercore.promises')
|
|
16
20
|
const inspect = Symbol.for('nodejs.util.inspect.custom')
|
|
@@ -27,14 +31,17 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
27
31
|
opts = key
|
|
28
32
|
key = null
|
|
29
33
|
}
|
|
34
|
+
|
|
30
35
|
if (key && typeof key === 'string') {
|
|
31
|
-
key =
|
|
36
|
+
key = b4a.from(key, 'hex')
|
|
32
37
|
}
|
|
33
|
-
|
|
38
|
+
|
|
39
|
+
if (!opts) opts = {}
|
|
40
|
+
|
|
41
|
+
if (!opts.crypto && key && key.byteLength !== 32) {
|
|
34
42
|
throw new Error('Hypercore key should be 32 bytes')
|
|
35
43
|
}
|
|
36
44
|
|
|
37
|
-
if (!opts) opts = {}
|
|
38
45
|
if (!storage) storage = opts.storage
|
|
39
46
|
|
|
40
47
|
this[promises] = true
|
|
@@ -43,7 +50,9 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
43
50
|
this.crypto = opts.crypto || hypercoreCrypto
|
|
44
51
|
this.core = null
|
|
45
52
|
this.replicator = null
|
|
53
|
+
this.encryption = null
|
|
46
54
|
this.extensions = opts.extensions || new Extensions()
|
|
55
|
+
this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
|
|
47
56
|
|
|
48
57
|
this.valueEncoding = null
|
|
49
58
|
this.key = key || null
|
|
@@ -57,8 +66,10 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
57
66
|
this.autoClose = !!opts.autoClose
|
|
58
67
|
|
|
59
68
|
this.closing = null
|
|
60
|
-
this.opening =
|
|
69
|
+
this.opening = this._openSession(key, storage, opts)
|
|
61
70
|
this.opening.catch(noop)
|
|
71
|
+
|
|
72
|
+
this._preappend = preappend.bind(this)
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
[inspect] (depth, opts) {
|
|
@@ -89,8 +100,9 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
89
100
|
const directory = storage
|
|
90
101
|
const toLock = opts.lock || 'oplog'
|
|
91
102
|
return function createFile (name) {
|
|
92
|
-
const
|
|
93
|
-
const
|
|
103
|
+
const locked = name === toLock || name.endsWith('/' + toLock)
|
|
104
|
+
const lock = locked ? fsctl.lock : null
|
|
105
|
+
const sparse = locked ? null : null // fsctl.sparse, disable sparse on windows - seems to fail for some people. TODO: investigate
|
|
94
106
|
return raf(name, { directory, lock, sparse })
|
|
95
107
|
}
|
|
96
108
|
}
|
|
@@ -103,28 +115,20 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
const Clz = opts.class || Hypercore
|
|
106
|
-
const keyPair = opts.keyPair && opts.keyPair.secretKey && { ...opts.keyPair }
|
|
107
|
-
|
|
108
|
-
// This only works if the hypercore was fully loaded,
|
|
109
|
-
// but we only do this to validate the keypair to help catch bugs so yolo
|
|
110
|
-
if (this.key && keyPair) keyPair.publicKey = this.key
|
|
111
|
-
|
|
112
118
|
const s = new Clz(this.storage, this.key, {
|
|
113
119
|
...opts,
|
|
114
|
-
sign: opts.sign || (keyPair && keyPair.secretKey && Core.createSigner(this.crypto, keyPair)) || this.sign,
|
|
115
|
-
valueEncoding: this.valueEncoding,
|
|
116
120
|
extensions: this.extensions,
|
|
117
121
|
_opening: this.opening,
|
|
118
122
|
_sessions: this.sessions
|
|
119
123
|
})
|
|
120
124
|
|
|
121
|
-
s.
|
|
125
|
+
s._passCapabilities(this)
|
|
122
126
|
this.sessions.push(s)
|
|
123
127
|
|
|
124
128
|
return s
|
|
125
129
|
}
|
|
126
130
|
|
|
127
|
-
|
|
131
|
+
_passCapabilities (o) {
|
|
128
132
|
if (!this.sign) this.sign = o.sign
|
|
129
133
|
this.crypto = o.crypto
|
|
130
134
|
this.opened = o.opened
|
|
@@ -132,10 +136,103 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
132
136
|
this.discoveryKey = o.discoveryKey
|
|
133
137
|
this.core = o.core
|
|
134
138
|
this.replicator = o.replicator
|
|
139
|
+
this.encryption = o.encryption
|
|
135
140
|
this.writable = !!this.sign
|
|
136
141
|
this.autoClose = o.autoClose
|
|
137
142
|
}
|
|
138
143
|
|
|
144
|
+
async _openFromExisting (from, opts) {
|
|
145
|
+
await from.opening
|
|
146
|
+
|
|
147
|
+
for (const [name, ext] of this.extensions) {
|
|
148
|
+
from.extensions.register(name, null, ext)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this._passCapabilities(from)
|
|
152
|
+
this.extensions = from.extensions
|
|
153
|
+
this.sessions = from.sessions
|
|
154
|
+
this.storage = from.storage
|
|
155
|
+
|
|
156
|
+
this.sessions.push(this)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async _openSession (key, storage, opts) {
|
|
160
|
+
const isFirst = !opts._opening
|
|
161
|
+
|
|
162
|
+
if (!isFirst) await opts._opening
|
|
163
|
+
if (opts.preload) opts = { ...opts, ...(await opts.preload()) }
|
|
164
|
+
|
|
165
|
+
const keyPair = (key && opts.keyPair)
|
|
166
|
+
? { ...opts.keyPair, publicKey: key }
|
|
167
|
+
: key
|
|
168
|
+
? { publicKey: key, secretKey: null }
|
|
169
|
+
: opts.keyPair
|
|
170
|
+
|
|
171
|
+
// This only works if the hypercore was fully loaded,
|
|
172
|
+
// but we only do this to validate the keypair to help catch bugs so yolo
|
|
173
|
+
if (this.key && keyPair) keyPair.publicKey = this.key
|
|
174
|
+
|
|
175
|
+
if (opts.sign) {
|
|
176
|
+
this.sign = opts.sign
|
|
177
|
+
} else if (keyPair && keyPair.secretKey) {
|
|
178
|
+
this.sign = Core.createSigner(this.crypto, keyPair)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (isFirst) {
|
|
182
|
+
await this._openCapabilities(keyPair, storage, opts)
|
|
183
|
+
// Only the root session should pass capabilities to other sessions.
|
|
184
|
+
for (let i = 0; i < this.sessions.length; i++) {
|
|
185
|
+
const s = this.sessions[i]
|
|
186
|
+
if (s !== this) s._passCapabilities(this)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!this.sign) this.sign = this.core.defaultSign
|
|
191
|
+
this.writable = !!this.sign
|
|
192
|
+
|
|
193
|
+
if (opts.valueEncoding) {
|
|
194
|
+
this.valueEncoding = c.from(codecs(opts.valueEncoding))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// This is a hidden option that's only used by Corestore.
|
|
198
|
+
// It's required so that corestore can load a name from userData before 'ready' is emitted.
|
|
199
|
+
if (opts._preready) await opts._preready(this)
|
|
200
|
+
|
|
201
|
+
this.opened = true
|
|
202
|
+
this.emit('ready')
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async _openCapabilities (keyPair, storage, opts) {
|
|
206
|
+
if (opts.from) return this._openFromExisting(opts.from, opts)
|
|
207
|
+
|
|
208
|
+
if (!this.storage) this.storage = Hypercore.defaultStorage(opts.storage || storage)
|
|
209
|
+
|
|
210
|
+
this.core = await Core.open(this.storage, {
|
|
211
|
+
keyPair,
|
|
212
|
+
crypto: this.crypto,
|
|
213
|
+
onupdate: this._oncoreupdate.bind(this)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
if (opts.userData) {
|
|
217
|
+
for (const [key, value] of Object.entries(opts.userData)) {
|
|
218
|
+
await this.core.userData(key, value)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
this.replicator = new Replicator(this.core, {
|
|
223
|
+
onupdate: this._onpeerupdate.bind(this)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
|
|
227
|
+
this.key = this.core.header.signer.publicKey
|
|
228
|
+
|
|
229
|
+
if (!this.encryption && opts.encryptionKey) {
|
|
230
|
+
this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
this.extensions.attach(this.replicator)
|
|
234
|
+
}
|
|
235
|
+
|
|
139
236
|
close () {
|
|
140
237
|
if (this.closing) return this.closing
|
|
141
238
|
this.closing = this._close()
|
|
@@ -201,7 +298,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
201
298
|
}
|
|
202
299
|
|
|
203
300
|
get byteLength () {
|
|
204
|
-
return this.core === null ? 0 : this.core.tree.byteLength
|
|
301
|
+
return this.core === null ? 0 : this.core.tree.byteLength - (this.core.tree.length * this.padding)
|
|
205
302
|
}
|
|
206
303
|
|
|
207
304
|
get fork () {
|
|
@@ -212,76 +309,28 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
212
309
|
return this.replicator === null ? [] : this.replicator.peers
|
|
213
310
|
}
|
|
214
311
|
|
|
215
|
-
|
|
216
|
-
return this.
|
|
312
|
+
get encryptionKey () {
|
|
313
|
+
return this.encryption && this.encryption.key
|
|
217
314
|
}
|
|
218
315
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
this.valueEncoding = opts.valueEncoding ? c.from(codecs(opts.valueEncoding)) : null
|
|
223
|
-
|
|
224
|
-
const keyPair = (key && opts.keyPair)
|
|
225
|
-
? { ...opts.keyPair, publicKey: key }
|
|
226
|
-
: key
|
|
227
|
-
? { publicKey: key, secretKey: null }
|
|
228
|
-
: opts.keyPair
|
|
229
|
-
|
|
230
|
-
if (opts.from) {
|
|
231
|
-
const from = opts.from
|
|
232
|
-
await from.opening
|
|
233
|
-
for (const [name, ext] of this.extensions) from.extensions.register(name, null, ext)
|
|
234
|
-
this._initSession(from)
|
|
235
|
-
this.extensions = from.extensions
|
|
236
|
-
this.sessions = from.sessions
|
|
237
|
-
this.storage = from.storage
|
|
238
|
-
if (!this.sign) this.sign = opts.sign || ((keyPair && keyPair.secretKey) ? Core.createSigner(this.crypto, keyPair) : null)
|
|
239
|
-
this.writable = !!this.sign
|
|
240
|
-
this.sessions.push(this)
|
|
241
|
-
return
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (!this.storage) this.storage = Hypercore.defaultStorage(opts.storage || storage)
|
|
245
|
-
|
|
246
|
-
this.core = await Core.open(this.storage, {
|
|
247
|
-
keyPair,
|
|
248
|
-
crypto: this.crypto,
|
|
249
|
-
onupdate: this._oncoreupdate.bind(this)
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
if (opts.userData) {
|
|
253
|
-
for (const [key, value] of Object.entries(opts.userData)) {
|
|
254
|
-
await this.core.userData(key, value)
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
this.replicator = new Replicator(this.core, {
|
|
259
|
-
onupdate: this._onpeerupdate.bind(this)
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
if (!this.sign) this.sign = opts.sign || this.core.defaultSign
|
|
263
|
-
|
|
264
|
-
this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
|
|
265
|
-
this.key = this.core.header.signer.publicKey
|
|
266
|
-
this.writable = !!this.sign
|
|
267
|
-
|
|
268
|
-
this.extensions.attach(this.replicator)
|
|
269
|
-
this.opened = true
|
|
270
|
-
|
|
271
|
-
if (opts.postload) await opts.postload(this)
|
|
316
|
+
get padding () {
|
|
317
|
+
return this.encryption === null ? 0 : this.encryption.padding
|
|
318
|
+
}
|
|
272
319
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (s !== this) s._initSession(this)
|
|
276
|
-
s.emit('ready')
|
|
277
|
-
}
|
|
320
|
+
ready () {
|
|
321
|
+
return this.opening
|
|
278
322
|
}
|
|
279
323
|
|
|
280
324
|
_oncoreupdate (status, bitfield, value, from) {
|
|
281
325
|
if (status !== 0) {
|
|
282
326
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
283
|
-
if ((status & 0b10) !== 0)
|
|
284
|
-
|
|
327
|
+
if ((status & 0b10) !== 0) {
|
|
328
|
+
if (this.cache) this.cache.clear()
|
|
329
|
+
this.sessions[i].emit('truncate', this.core.tree.fork)
|
|
330
|
+
}
|
|
331
|
+
if ((status & 0b01) !== 0) {
|
|
332
|
+
this.sessions[i].emit('append')
|
|
333
|
+
}
|
|
285
334
|
}
|
|
286
335
|
|
|
287
336
|
this.replicator.broadcastInfo()
|
|
@@ -294,8 +343,10 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
294
343
|
}
|
|
295
344
|
|
|
296
345
|
if (value) {
|
|
346
|
+
const byteLength = value.byteLength - this.padding
|
|
347
|
+
|
|
297
348
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
298
|
-
this.sessions[i].emit('download', bitfield.start,
|
|
349
|
+
this.sessions[i].emit('download', bitfield.start, byteLength, from)
|
|
299
350
|
}
|
|
300
351
|
}
|
|
301
352
|
}
|
|
@@ -332,7 +383,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
332
383
|
async seek (bytes) {
|
|
333
384
|
if (this.opened === false) await this.opening
|
|
334
385
|
|
|
335
|
-
const s = this.core.tree.seek(bytes)
|
|
386
|
+
const s = this.core.tree.seek(bytes, this.padding)
|
|
336
387
|
|
|
337
388
|
return (await s.update()) || this.replicator.requestSeek(s)
|
|
338
389
|
}
|
|
@@ -345,22 +396,56 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
345
396
|
|
|
346
397
|
async get (index, opts) {
|
|
347
398
|
if (this.opened === false) await this.opening
|
|
399
|
+
const c = this.cache && this.cache.get(index)
|
|
400
|
+
if (c) return c
|
|
401
|
+
const fork = this.core.tree.fork
|
|
402
|
+
const b = await this._get(index, opts)
|
|
403
|
+
if (this.cache && fork === this.core.tree.fork && b) this.cache.set(index, b)
|
|
404
|
+
return b
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async _get (index, opts) {
|
|
348
408
|
const encoding = (opts && opts.valueEncoding && c.from(codecs(opts.valueEncoding))) || this.valueEncoding
|
|
349
409
|
|
|
350
|
-
|
|
351
|
-
|
|
410
|
+
let block
|
|
411
|
+
|
|
412
|
+
if (this.core.bitfield.get(index)) {
|
|
413
|
+
block = await this.core.blocks.get(index)
|
|
414
|
+
} else {
|
|
415
|
+
if (opts && opts.onwait) opts.onwait(index)
|
|
416
|
+
block = await this.replicator.requestBlock(index)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (this.encryption) this.encryption.decrypt(index, block)
|
|
420
|
+
return this._decode(encoding, block)
|
|
421
|
+
}
|
|
352
422
|
|
|
353
|
-
|
|
423
|
+
createReadStream (opts) {
|
|
424
|
+
return new ReadStream(this, opts)
|
|
354
425
|
}
|
|
355
426
|
|
|
356
427
|
download (range) {
|
|
357
|
-
const start = (range && range.start) || 0
|
|
358
|
-
const end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
|
|
359
428
|
const linear = !!(range && range.linear)
|
|
360
429
|
|
|
361
|
-
|
|
430
|
+
let start
|
|
431
|
+
let end
|
|
432
|
+
let filter
|
|
362
433
|
|
|
363
|
-
|
|
434
|
+
if (range && range.blocks) {
|
|
435
|
+
const blocks = range.blocks instanceof Set
|
|
436
|
+
? range.blocks
|
|
437
|
+
: new Set(range.blocks)
|
|
438
|
+
|
|
439
|
+
start = range.start || (blocks.size ? min(range.blocks) : 0)
|
|
440
|
+
end = range.end || (blocks.size ? max(range.blocks) + 1 : 0)
|
|
441
|
+
|
|
442
|
+
filter = (i) => blocks.has(i)
|
|
443
|
+
} else {
|
|
444
|
+
start = (range && range.start) || 0
|
|
445
|
+
end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const r = Replicator.createRange(start, end, filter, linear)
|
|
364
449
|
|
|
365
450
|
if (this.opened) this.replicator.addRange(r)
|
|
366
451
|
else this.opening.then(() => this.replicator.addRange(r), noop)
|
|
@@ -393,22 +478,16 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
393
478
|
if (this.opened === false) await this.opening
|
|
394
479
|
if (this.writable === false) throw new Error('Core is not writable')
|
|
395
480
|
|
|
396
|
-
|
|
397
|
-
const buffers = new Array(blks.length)
|
|
481
|
+
blocks = Array.isArray(blocks) ? blocks : [blocks]
|
|
398
482
|
|
|
399
|
-
|
|
400
|
-
|
|
483
|
+
const preappend = this.encryption && this._preappend
|
|
484
|
+
const buffers = new Array(blocks.length)
|
|
401
485
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
: this.valueEncoding
|
|
405
|
-
? c.encode(this.valueEncoding, blk)
|
|
406
|
-
: Buffer.from(blk)
|
|
407
|
-
|
|
408
|
-
buffers[i] = buf
|
|
486
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
487
|
+
buffers[i] = this._encode(this.valueEncoding, blocks[i])
|
|
409
488
|
}
|
|
410
489
|
|
|
411
|
-
return await this.core.append(buffers, this.sign)
|
|
490
|
+
return await this.core.append(buffers, this.sign, { preappend })
|
|
412
491
|
}
|
|
413
492
|
|
|
414
493
|
registerExtension (name, handlers) {
|
|
@@ -419,14 +498,38 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
419
498
|
onextensionupdate () {
|
|
420
499
|
if (this.replicator !== null) this.replicator.broadcastOptions()
|
|
421
500
|
}
|
|
422
|
-
}
|
|
423
501
|
|
|
424
|
-
|
|
502
|
+
_encode (enc, val) {
|
|
503
|
+
const state = { start: this.padding, end: this.padding, buffer: null }
|
|
504
|
+
|
|
505
|
+
if (b4a.isBuffer(val)) {
|
|
506
|
+
if (state.start === 0) return val
|
|
507
|
+
state.end += val.byteLength
|
|
508
|
+
} else if (enc) {
|
|
509
|
+
enc.preencode(state, val)
|
|
510
|
+
} else {
|
|
511
|
+
val = b4a.from(val)
|
|
512
|
+
if (state.start === 0) return val
|
|
513
|
+
state.end += val.byteLength
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
517
|
+
|
|
518
|
+
if (enc) enc.encode(state, val)
|
|
519
|
+
else state.buffer.set(val, state.start)
|
|
520
|
+
|
|
521
|
+
return state.buffer
|
|
522
|
+
}
|
|
425
523
|
|
|
426
|
-
|
|
427
|
-
|
|
524
|
+
_decode (enc, block) {
|
|
525
|
+
block = block.subarray(this.padding)
|
|
526
|
+
if (enc) return c.decode(enc, block)
|
|
527
|
+
return block
|
|
528
|
+
}
|
|
428
529
|
}
|
|
429
530
|
|
|
531
|
+
function noop () {}
|
|
532
|
+
|
|
430
533
|
function isStream (s) {
|
|
431
534
|
return typeof s === 'object' && s && typeof s.pipe === 'function'
|
|
432
535
|
}
|
|
@@ -440,5 +543,27 @@ function requireMaybe (name) {
|
|
|
440
543
|
}
|
|
441
544
|
|
|
442
545
|
function toHex (buf) {
|
|
443
|
-
return buf &&
|
|
546
|
+
return buf && b4a.toString(buf, 'hex')
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function reduce (iter, fn, acc) {
|
|
550
|
+
for (const item of iter) acc = fn(acc, item)
|
|
551
|
+
return acc
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function min (arr) {
|
|
555
|
+
return reduce(arr, (a, b) => Math.min(a, b), Infinity)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function max (arr) {
|
|
559
|
+
return reduce(arr, (a, b) => Math.max(a, b), -Infinity)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function preappend (blocks) {
|
|
563
|
+
const offset = this.core.tree.length
|
|
564
|
+
const fork = this.core.tree.fork
|
|
565
|
+
|
|
566
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
567
|
+
this.encryption.encrypt(offset + i, blocks[i], fork)
|
|
568
|
+
}
|
|
444
569
|
}
|
package/lib/bitfield.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// TODO: needs massive improvements obvs
|
|
2
2
|
|
|
3
3
|
const BigSparseArray = require('big-sparse-array')
|
|
4
|
+
const b4a = require('b4a')
|
|
4
5
|
|
|
5
6
|
class FixedBitfield {
|
|
6
7
|
constructor (index, bitfield) {
|
|
@@ -116,9 +117,9 @@ module.exports = class Bitfield {
|
|
|
116
117
|
let error = null
|
|
117
118
|
|
|
118
119
|
for (const page of this.unflushed) {
|
|
119
|
-
const
|
|
120
|
+
const buf = b4a.from(page.bitfield.buffer, page.bitfield.byteOffset, page.bitfield.byteLength)
|
|
120
121
|
page.dirty = false
|
|
121
|
-
this.storage.write(page.index * 4096,
|
|
122
|
+
this.storage.write(page.index * 4096, buf, done)
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
function done (err) {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const sodium = require('sodium-universal')
|
|
2
|
+
const c = require('compact-encoding')
|
|
3
|
+
const b4a = require('b4a')
|
|
4
|
+
|
|
5
|
+
const nonce = b4a.alloc(sodium.crypto_stream_NONCEBYTES)
|
|
6
|
+
|
|
7
|
+
module.exports = class BlockEncryption {
|
|
8
|
+
constructor (encryptionKey, hypercoreKey) {
|
|
9
|
+
const subKeys = b4a.alloc(2 * sodium.crypto_stream_KEYBYTES)
|
|
10
|
+
|
|
11
|
+
this.key = encryptionKey
|
|
12
|
+
this.blockKey = subKeys.subarray(0, sodium.crypto_stream_KEYBYTES)
|
|
13
|
+
this.blindingKey = subKeys.subarray(sodium.crypto_stream_KEYBYTES)
|
|
14
|
+
this.padding = 8
|
|
15
|
+
|
|
16
|
+
sodium.crypto_generichash(this.blockKey, encryptionKey, hypercoreKey)
|
|
17
|
+
sodium.crypto_generichash(this.blindingKey, this.blockKey)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
encrypt (index, block, fork) {
|
|
21
|
+
const padding = block.subarray(0, this.padding)
|
|
22
|
+
block = block.subarray(this.padding)
|
|
23
|
+
|
|
24
|
+
c.uint64.encode({ start: 0, end: 8, buffer: padding }, fork)
|
|
25
|
+
c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)
|
|
26
|
+
|
|
27
|
+
// Zero out any previous padding.
|
|
28
|
+
nonce.fill(0, 8, 8 + padding.byteLength)
|
|
29
|
+
|
|
30
|
+
// Blind the fork ID, possibly risking reusing the nonce on a reorg of the
|
|
31
|
+
// Hypercore. This is fine as the blinding is best-effort and the latest
|
|
32
|
+
// fork ID shared on replication anyway.
|
|
33
|
+
sodium.crypto_stream_xor(
|
|
34
|
+
padding,
|
|
35
|
+
padding,
|
|
36
|
+
nonce,
|
|
37
|
+
this.blindingKey
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
nonce.set(padding, 8)
|
|
41
|
+
|
|
42
|
+
// The combination of a (blinded) fork ID and a block index is unique for a
|
|
43
|
+
// given Hypercore and is therefore a valid nonce for encrypting the block.
|
|
44
|
+
sodium.crypto_stream_xor(
|
|
45
|
+
block,
|
|
46
|
+
block,
|
|
47
|
+
nonce,
|
|
48
|
+
this.blockKey
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
decrypt (index, block) {
|
|
53
|
+
const padding = block.subarray(0, this.padding)
|
|
54
|
+
block = block.subarray(this.padding)
|
|
55
|
+
|
|
56
|
+
c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)
|
|
57
|
+
|
|
58
|
+
nonce.set(padding, 8)
|
|
59
|
+
|
|
60
|
+
// Decrypt the block using the blinded fork ID.
|
|
61
|
+
sodium.crypto_stream_xor(
|
|
62
|
+
block,
|
|
63
|
+
block,
|
|
64
|
+
nonce,
|
|
65
|
+
this.blockKey
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
package/lib/block-store.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const b4a = require('b4a')
|
|
2
|
+
|
|
1
3
|
module.exports = class BlockStore {
|
|
2
4
|
constructor (storage, tree) {
|
|
3
5
|
this.storage = storage
|
|
@@ -15,7 +17,7 @@ module.exports = class BlockStore {
|
|
|
15
17
|
|
|
16
18
|
putBatch (i, batch, offset) {
|
|
17
19
|
if (batch.length === 0) return Promise.resolve()
|
|
18
|
-
return this.put(i, batch.length === 1 ? batch[0] :
|
|
20
|
+
return this.put(i, batch.length === 1 ? batch[0] : b4a.concat(batch), offset)
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
clear () {
|
package/lib/core.js
CHANGED
|
@@ -214,10 +214,12 @@ module.exports = class Core {
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
async append (values, sign = this.defaultSign) {
|
|
217
|
+
async append (values, sign = this.defaultSign, hooks = {}) {
|
|
218
218
|
await this._mutex.lock()
|
|
219
219
|
|
|
220
220
|
try {
|
|
221
|
+
if (hooks.preappend) await hooks.preappend(values)
|
|
222
|
+
|
|
221
223
|
if (!values.length) return this.tree.length
|
|
222
224
|
|
|
223
225
|
const batch = this.tree.batch()
|