hypercore 10.0.0-alpha.5 → 10.0.0-alpha.9
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/README.md +17 -1
- package/index.js +115 -27
- package/lib/block-encryption.js +67 -0
- package/lib/core.js +3 -1
- package/lib/merkle-tree.js +20 -13
- package/lib/replicator.js +26 -8
- package/package.json +4 -3
- package/test/basic.js +12 -0
- package/test/encryption.js +121 -0
- package/test/replicate.js +39 -0
package/README.md
CHANGED
|
@@ -63,7 +63,8 @@ Note that `tree`, `data`, and `bitfield` are normally heavily sparse files.
|
|
|
63
63
|
createIfMissing: true, // create a new Hypercore key pair if none was present in storage
|
|
64
64
|
overwrite: false, // overwrite any old Hypercore that might already exist
|
|
65
65
|
valueEncoding: 'json' | 'utf-8' | 'binary', // defaults to binary
|
|
66
|
-
keyPair: kp // optionally pass the public key and secret key as a key pair
|
|
66
|
+
keyPair: kp, // optionally pass the public key and secret key as a key pair
|
|
67
|
+
encryptionKey: k // optionally pass an encryption key to enable block encryption
|
|
67
68
|
}
|
|
68
69
|
```
|
|
69
70
|
|
|
@@ -113,6 +114,7 @@ A range can have the following properties:
|
|
|
113
114
|
{
|
|
114
115
|
start: startIndex,
|
|
115
116
|
end: nonInclusiveEndIndex,
|
|
117
|
+
blocks: [index1, index2, ...],
|
|
116
118
|
linear: false // download range linearly and not randomly
|
|
117
119
|
}
|
|
118
120
|
```
|
|
@@ -125,6 +127,12 @@ To download the full core continously (often referred to as non sparse mode) do
|
|
|
125
127
|
core.download({ start: 0, end: -1 })
|
|
126
128
|
```
|
|
127
129
|
|
|
130
|
+
To downloaded a discrete range of blocks pass a list of indices.
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
core.download({ blocks: [4, 9, 7] });
|
|
134
|
+
```
|
|
135
|
+
|
|
128
136
|
To cancel downloading a range simply destroy the range instance.
|
|
129
137
|
|
|
130
138
|
``` js
|
|
@@ -195,6 +203,10 @@ In contrast to `core.key` this key does not allow you to verify the data but can
|
|
|
195
203
|
|
|
196
204
|
Populated after `ready` has been emitted. Will be `null` before the event.
|
|
197
205
|
|
|
206
|
+
#### `core.encryptionKey`
|
|
207
|
+
|
|
208
|
+
Buffer containing the optional block encryption key of this core. Will be `null` unless block encryption is enabled.
|
|
209
|
+
|
|
198
210
|
#### `core.length`
|
|
199
211
|
|
|
200
212
|
How many blocks of data are available on this core?
|
|
@@ -213,6 +225,10 @@ What is the current fork id of this core?
|
|
|
213
225
|
|
|
214
226
|
Populated after `ready` has been emitted. Will be `0` before the event.
|
|
215
227
|
|
|
228
|
+
#### `core.padding`
|
|
229
|
+
|
|
230
|
+
How much padding is applied to each block of this core? Will be `0` unless block encryption is enabled.
|
|
231
|
+
|
|
216
232
|
#### `const stream = core.replicate(isInitiatorOrReplicationStream)`
|
|
217
233
|
|
|
218
234
|
Create a replication stream. You should pipe this to another Hypercore instance.
|
package/index.js
CHANGED
|
@@ -12,6 +12,7 @@ const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
|
|
|
12
12
|
const Replicator = require('./lib/replicator')
|
|
13
13
|
const Extensions = require('./lib/extensions')
|
|
14
14
|
const Core = require('./lib/core')
|
|
15
|
+
const BlockEncryption = require('./lib/block-encryption')
|
|
15
16
|
|
|
16
17
|
const promises = Symbol.for('hypercore.promises')
|
|
17
18
|
const inspect = Symbol.for('nodejs.util.inspect.custom')
|
|
@@ -28,14 +29,17 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
28
29
|
opts = key
|
|
29
30
|
key = null
|
|
30
31
|
}
|
|
32
|
+
|
|
31
33
|
if (key && typeof key === 'string') {
|
|
32
34
|
key = Buffer.from(key, 'hex')
|
|
33
35
|
}
|
|
34
|
-
|
|
36
|
+
|
|
37
|
+
if (!opts) opts = {}
|
|
38
|
+
|
|
39
|
+
if (!opts.crypto && key && key.byteLength !== 32) {
|
|
35
40
|
throw new Error('Hypercore key should be 32 bytes')
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
if (!opts) opts = {}
|
|
39
43
|
if (!storage) storage = opts.storage
|
|
40
44
|
|
|
41
45
|
this[promises] = true
|
|
@@ -44,6 +48,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
44
48
|
this.crypto = opts.crypto || hypercoreCrypto
|
|
45
49
|
this.core = null
|
|
46
50
|
this.replicator = null
|
|
51
|
+
this.encryption = null
|
|
47
52
|
this.extensions = opts.extensions || new Extensions()
|
|
48
53
|
this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
|
|
49
54
|
|
|
@@ -61,6 +66,8 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
61
66
|
this.closing = null
|
|
62
67
|
this.opening = opts._opening || this._open(key, storage, opts)
|
|
63
68
|
this.opening.catch(noop)
|
|
69
|
+
|
|
70
|
+
this._preappend = preappend.bind(this)
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
[inspect] (depth, opts) {
|
|
@@ -135,6 +142,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
135
142
|
this.discoveryKey = o.discoveryKey
|
|
136
143
|
this.core = o.core
|
|
137
144
|
this.replicator = o.replicator
|
|
145
|
+
this.encryption = o.encryption
|
|
138
146
|
this.writable = !!this.sign
|
|
139
147
|
this.autoClose = o.autoClose
|
|
140
148
|
}
|
|
@@ -204,7 +212,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
204
212
|
}
|
|
205
213
|
|
|
206
214
|
get byteLength () {
|
|
207
|
-
return this.core === null ? 0 : this.core.tree.byteLength
|
|
215
|
+
return this.core === null ? 0 : this.core.tree.byteLength - (this.core.tree.length * this.padding)
|
|
208
216
|
}
|
|
209
217
|
|
|
210
218
|
get fork () {
|
|
@@ -215,6 +223,14 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
215
223
|
return this.replicator === null ? [] : this.replicator.peers
|
|
216
224
|
}
|
|
217
225
|
|
|
226
|
+
get encryptionKey () {
|
|
227
|
+
return this.encryption && this.encryption.key
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
get padding () {
|
|
231
|
+
return this.encryption === null ? 0 : this.encryption.padding
|
|
232
|
+
}
|
|
233
|
+
|
|
218
234
|
ready () {
|
|
219
235
|
return this.opening
|
|
220
236
|
}
|
|
@@ -268,6 +284,10 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
268
284
|
this.key = this.core.header.signer.publicKey
|
|
269
285
|
this.writable = !!this.sign
|
|
270
286
|
|
|
287
|
+
if (!this.encryption && opts.encryptionKey) {
|
|
288
|
+
this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
|
|
289
|
+
}
|
|
290
|
+
|
|
271
291
|
this.extensions.attach(this.replicator)
|
|
272
292
|
this.opened = true
|
|
273
293
|
|
|
@@ -302,6 +322,12 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
302
322
|
}
|
|
303
323
|
|
|
304
324
|
if (value) {
|
|
325
|
+
if (this.encryption) {
|
|
326
|
+
this.encryption.decrypt(bitfield.start, value)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
value = value.subarray(this.padding)
|
|
330
|
+
|
|
305
331
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
306
332
|
this.sessions[i].emit('download', bitfield.start, value, from)
|
|
307
333
|
}
|
|
@@ -340,7 +366,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
340
366
|
async seek (bytes) {
|
|
341
367
|
if (this.opened === false) await this.opening
|
|
342
368
|
|
|
343
|
-
const s = this.core.tree.seek(bytes)
|
|
369
|
+
const s = this.core.tree.seek(bytes, this.padding)
|
|
344
370
|
|
|
345
371
|
return (await s.update()) || this.replicator.requestSeek(s)
|
|
346
372
|
}
|
|
@@ -364,20 +390,42 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
364
390
|
async _get (index, opts) {
|
|
365
391
|
const encoding = (opts && opts.valueEncoding && c.from(codecs(opts.valueEncoding))) || this.valueEncoding
|
|
366
392
|
|
|
367
|
-
|
|
368
|
-
|
|
393
|
+
let block
|
|
394
|
+
|
|
395
|
+
if (this.core.bitfield.get(index)) {
|
|
396
|
+
block = await this.core.blocks.get(index)
|
|
397
|
+
if (this.encryption) this.encryption.decrypt(index, block)
|
|
398
|
+
} else {
|
|
399
|
+
if (opts && opts.onwait) opts.onwait(index)
|
|
400
|
+
// Note that the _oncoreupdate handler decrypts inplace so we should not decrypt here
|
|
401
|
+
block = await this.replicator.requestBlock(index)
|
|
402
|
+
}
|
|
369
403
|
|
|
370
|
-
return
|
|
404
|
+
return this._decode(encoding, block)
|
|
371
405
|
}
|
|
372
406
|
|
|
373
407
|
download (range) {
|
|
374
|
-
const start = (range && range.start) || 0
|
|
375
|
-
const end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
|
|
376
408
|
const linear = !!(range && range.linear)
|
|
377
409
|
|
|
378
|
-
|
|
410
|
+
let start
|
|
411
|
+
let end
|
|
412
|
+
let filter
|
|
413
|
+
|
|
414
|
+
if (range && range.blocks) {
|
|
415
|
+
const blocks = range.blocks instanceof Set
|
|
416
|
+
? range.blocks
|
|
417
|
+
: new Set(range.blocks)
|
|
418
|
+
|
|
419
|
+
start = range.start || (blocks.size ? min(range.blocks) : 0)
|
|
420
|
+
end = range.end || (blocks.size ? max(range.blocks) + 1 : 0)
|
|
421
|
+
|
|
422
|
+
filter = (i) => blocks.has(i)
|
|
423
|
+
} else {
|
|
424
|
+
start = (range && range.start) || 0
|
|
425
|
+
end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
|
|
426
|
+
}
|
|
379
427
|
|
|
380
|
-
const r = Replicator.createRange(start, end, linear)
|
|
428
|
+
const r = Replicator.createRange(start, end, filter, linear)
|
|
381
429
|
|
|
382
430
|
if (this.opened) this.replicator.addRange(r)
|
|
383
431
|
else this.opening.then(() => this.replicator.addRange(r), noop)
|
|
@@ -410,22 +458,16 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
410
458
|
if (this.opened === false) await this.opening
|
|
411
459
|
if (this.writable === false) throw new Error('Core is not writable')
|
|
412
460
|
|
|
413
|
-
|
|
414
|
-
const buffers = new Array(blks.length)
|
|
461
|
+
blocks = Array.isArray(blocks) ? blocks : [blocks]
|
|
415
462
|
|
|
416
|
-
|
|
417
|
-
|
|
463
|
+
const preappend = this.encryption && this._preappend
|
|
464
|
+
const buffers = new Array(blocks.length)
|
|
418
465
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
: this.valueEncoding
|
|
422
|
-
? c.encode(this.valueEncoding, blk)
|
|
423
|
-
: Buffer.from(blk)
|
|
424
|
-
|
|
425
|
-
buffers[i] = buf
|
|
466
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
467
|
+
buffers[i] = this._encode(this.valueEncoding, blocks[i])
|
|
426
468
|
}
|
|
427
469
|
|
|
428
|
-
return await this.core.append(buffers, this.sign)
|
|
470
|
+
return await this.core.append(buffers, this.sign, { preappend })
|
|
429
471
|
}
|
|
430
472
|
|
|
431
473
|
registerExtension (name, handlers) {
|
|
@@ -436,14 +478,38 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
436
478
|
onextensionupdate () {
|
|
437
479
|
if (this.replicator !== null) this.replicator.broadcastOptions()
|
|
438
480
|
}
|
|
439
|
-
}
|
|
440
481
|
|
|
441
|
-
|
|
482
|
+
_encode (enc, val) {
|
|
483
|
+
const state = { start: this.padding, end: this.padding, buffer: null }
|
|
484
|
+
|
|
485
|
+
if (Buffer.isBuffer(val)) {
|
|
486
|
+
if (state.start === 0) return val
|
|
487
|
+
state.end += val.byteLength
|
|
488
|
+
} else if (enc) {
|
|
489
|
+
enc.preencode(state, val)
|
|
490
|
+
} else {
|
|
491
|
+
val = Buffer.from(val)
|
|
492
|
+
if (state.start === 0) return val
|
|
493
|
+
state.end += val.byteLength
|
|
494
|
+
}
|
|
442
495
|
|
|
443
|
-
|
|
444
|
-
|
|
496
|
+
state.buffer = Buffer.allocUnsafe(state.end)
|
|
497
|
+
|
|
498
|
+
if (enc) enc.encode(state, val)
|
|
499
|
+
else state.buffer.set(val, state.start)
|
|
500
|
+
|
|
501
|
+
return state.buffer
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
_decode (enc, block) {
|
|
505
|
+
block = block.subarray(this.padding)
|
|
506
|
+
if (enc) return c.decode(enc, block)
|
|
507
|
+
return block
|
|
508
|
+
}
|
|
445
509
|
}
|
|
446
510
|
|
|
511
|
+
function noop () {}
|
|
512
|
+
|
|
447
513
|
function isStream (s) {
|
|
448
514
|
return typeof s === 'object' && s && typeof s.pipe === 'function'
|
|
449
515
|
}
|
|
@@ -459,3 +525,25 @@ function requireMaybe (name) {
|
|
|
459
525
|
function toHex (buf) {
|
|
460
526
|
return buf && buf.toString('hex')
|
|
461
527
|
}
|
|
528
|
+
|
|
529
|
+
function reduce (iter, fn, acc) {
|
|
530
|
+
for (const item of iter) acc = fn(acc, item)
|
|
531
|
+
return acc
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function min (arr) {
|
|
535
|
+
return reduce(arr, (a, b) => Math.min(a, b), Infinity)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function max (arr) {
|
|
539
|
+
return reduce(arr, (a, b) => Math.max(a, b), -Infinity)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function preappend (blocks) {
|
|
543
|
+
const offset = this.core.tree.length
|
|
544
|
+
const fork = this.core.tree.fork
|
|
545
|
+
|
|
546
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
547
|
+
this.encryption.encrypt(offset + i, blocks[i], fork)
|
|
548
|
+
}
|
|
549
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const sodium = require('sodium-universal')
|
|
2
|
+
const c = require('compact-encoding')
|
|
3
|
+
|
|
4
|
+
const nonce = Buffer.alloc(sodium.crypto_stream_NONCEBYTES)
|
|
5
|
+
|
|
6
|
+
module.exports = class BlockEncryption {
|
|
7
|
+
constructor (encryptionKey, hypercoreKey) {
|
|
8
|
+
const subKeys = Buffer.alloc(2 * sodium.crypto_stream_KEYBYTES)
|
|
9
|
+
|
|
10
|
+
this.key = encryptionKey
|
|
11
|
+
this.blockKey = subKeys.subarray(0, sodium.crypto_stream_KEYBYTES)
|
|
12
|
+
this.blindingKey = subKeys.subarray(sodium.crypto_stream_KEYBYTES)
|
|
13
|
+
this.padding = 8
|
|
14
|
+
|
|
15
|
+
sodium.crypto_generichash(this.blockKey, encryptionKey, hypercoreKey)
|
|
16
|
+
sodium.crypto_generichash(this.blindingKey, this.blockKey)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
encrypt (index, block, fork) {
|
|
20
|
+
const padding = block.subarray(0, this.padding)
|
|
21
|
+
block = block.subarray(this.padding)
|
|
22
|
+
|
|
23
|
+
c.uint64.encode({ start: 0, end: 8, buffer: padding }, fork)
|
|
24
|
+
c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)
|
|
25
|
+
|
|
26
|
+
// Zero out any previous padding.
|
|
27
|
+
nonce.fill(0, 8, 8 + padding.byteLength)
|
|
28
|
+
|
|
29
|
+
// Blind the fork ID, possibly risking reusing the nonce on a reorg of the
|
|
30
|
+
// Hypercore. This is fine as the blinding is best-effort and the latest
|
|
31
|
+
// fork ID shared on replication anyway.
|
|
32
|
+
sodium.crypto_stream_xor(
|
|
33
|
+
padding,
|
|
34
|
+
padding,
|
|
35
|
+
nonce,
|
|
36
|
+
this.blindingKey
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
nonce.set(padding, 8)
|
|
40
|
+
|
|
41
|
+
// The combination of a (blinded) fork ID and a block index is unique for a
|
|
42
|
+
// given Hypercore and is therefore a valid nonce for encrypting the block.
|
|
43
|
+
sodium.crypto_stream_xor(
|
|
44
|
+
block,
|
|
45
|
+
block,
|
|
46
|
+
nonce,
|
|
47
|
+
this.blockKey
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
decrypt (index, block) {
|
|
52
|
+
const padding = block.subarray(0, this.padding)
|
|
53
|
+
block = block.subarray(this.padding)
|
|
54
|
+
|
|
55
|
+
c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)
|
|
56
|
+
|
|
57
|
+
nonce.set(padding, 8)
|
|
58
|
+
|
|
59
|
+
// Decrypt the block using the blinded fork ID.
|
|
60
|
+
sodium.crypto_stream_xor(
|
|
61
|
+
block,
|
|
62
|
+
block,
|
|
63
|
+
nonce,
|
|
64
|
+
this.blockKey
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
}
|
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()
|
package/lib/merkle-tree.js
CHANGED
|
@@ -272,11 +272,15 @@ class ReorgBatch extends MerkleTreeBatch {
|
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
class ByteSeeker {
|
|
275
|
-
constructor (tree, bytes) {
|
|
275
|
+
constructor (tree, bytes, padding = 0) {
|
|
276
276
|
this.tree = tree
|
|
277
277
|
this.bytes = bytes
|
|
278
|
-
this.
|
|
279
|
-
|
|
278
|
+
this.padding = padding
|
|
279
|
+
|
|
280
|
+
const size = tree.byteLength - (tree.length * padding)
|
|
281
|
+
|
|
282
|
+
this.start = bytes >= size ? tree.length : 0
|
|
283
|
+
this.end = bytes < size ? tree.length : 0
|
|
280
284
|
}
|
|
281
285
|
|
|
282
286
|
nodes () {
|
|
@@ -287,12 +291,12 @@ class ByteSeeker {
|
|
|
287
291
|
if (!bytes) return [0, 0]
|
|
288
292
|
|
|
289
293
|
for (const node of this.tree.roots) { // all async ticks happen once we find the root so safe
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
294
|
+
let size = node.size
|
|
295
|
+
if (this.padding > 0) size -= this.padding * flat.countLeaves(node.index)
|
|
293
296
|
|
|
294
|
-
if (bytes
|
|
295
|
-
|
|
297
|
+
if (bytes === size) return [flat.rightSpan(node.index) + 2, 0]
|
|
298
|
+
if (bytes > size) {
|
|
299
|
+
bytes -= size
|
|
296
300
|
continue
|
|
297
301
|
}
|
|
298
302
|
|
|
@@ -301,9 +305,12 @@ class ByteSeeker {
|
|
|
301
305
|
while ((ite.index & 1) !== 0) {
|
|
302
306
|
const l = await this.tree.get(ite.leftChild(), false)
|
|
303
307
|
if (l) {
|
|
304
|
-
|
|
305
|
-
if (
|
|
306
|
-
|
|
308
|
+
let size = l.size
|
|
309
|
+
if (this.padding > 0) size -= this.padding * ite.countLeaves()
|
|
310
|
+
|
|
311
|
+
if (size === bytes) return [ite.rightSpan() + 2, 0]
|
|
312
|
+
if (size > bytes) continue
|
|
313
|
+
bytes -= size
|
|
307
314
|
ite.sibling()
|
|
308
315
|
} else {
|
|
309
316
|
ite.parent()
|
|
@@ -355,8 +362,8 @@ module.exports = class MerkleTree {
|
|
|
355
362
|
return new MerkleTreeBatch(this)
|
|
356
363
|
}
|
|
357
364
|
|
|
358
|
-
seek (bytes) {
|
|
359
|
-
return new ByteSeeker(this, bytes)
|
|
365
|
+
seek (bytes, padding) {
|
|
366
|
+
return new ByteSeeker(this, bytes, padding)
|
|
360
367
|
}
|
|
361
368
|
|
|
362
369
|
hash () {
|
package/lib/replicator.js
CHANGED
|
@@ -120,9 +120,10 @@ class Seek {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
class Range {
|
|
123
|
-
constructor (start, end, linear) {
|
|
123
|
+
constructor (start, end, filter, linear) {
|
|
124
124
|
this.start = start
|
|
125
125
|
this.end = end
|
|
126
|
+
this.filter = filter
|
|
126
127
|
this.linear = !!linear
|
|
127
128
|
this.promise = null
|
|
128
129
|
this.done = false
|
|
@@ -145,7 +146,7 @@ class Range {
|
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
for (; this._start < this.end; this._start++) {
|
|
148
|
-
if (!bitfield.get(this._start)) return false
|
|
149
|
+
if (this.filter(this._start) && !bitfield.get(this._start)) return false
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
return true
|
|
@@ -299,10 +300,13 @@ class RequestPool {
|
|
|
299
300
|
const end = range.end === -1 ? peer.state.length : range.end
|
|
300
301
|
if (end <= range._start) return false
|
|
301
302
|
|
|
302
|
-
if (range.linear) return !!this._requestRange(peer, range._start, end, 0, 0)
|
|
303
|
+
if (range.linear) return !!this._requestRange(peer, range._start, end, 0, 0, range.filter)
|
|
303
304
|
|
|
304
305
|
const r = range._start + Math.floor(Math.random() * (end - range._start))
|
|
305
|
-
return !!(
|
|
306
|
+
return !!(
|
|
307
|
+
this._requestRange(peer, r, end, 0, 0, range.filter) ||
|
|
308
|
+
this._requestRange(peer, range._start, r, 0, 0, range.filter)
|
|
309
|
+
)
|
|
306
310
|
}
|
|
307
311
|
|
|
308
312
|
_updateUpgrade (peer) {
|
|
@@ -331,7 +335,7 @@ class RequestPool {
|
|
|
331
335
|
this.pendingUpgrade = null
|
|
332
336
|
}
|
|
333
337
|
|
|
334
|
-
_requestRange (peer, start, end, seek, nodes) {
|
|
338
|
+
_requestRange (peer, start, end, seek, nodes, filter = tautology) {
|
|
335
339
|
const remote = peer.state.bitfield
|
|
336
340
|
const local = this.core.bitfield
|
|
337
341
|
|
|
@@ -339,7 +343,7 @@ class RequestPool {
|
|
|
339
343
|
if (end === -1) end = peer.state.length
|
|
340
344
|
|
|
341
345
|
for (let i = start; i < end; i++) {
|
|
342
|
-
if (!remote.get(i) || local.get(i)) continue
|
|
346
|
+
if (!filter(i) || !remote.get(i) || local.get(i)) continue
|
|
343
347
|
// TODO: if this was a NO_VALUE request, retry if no blocks can be found elsewhere
|
|
344
348
|
if (this.requests.has(i)) continue
|
|
345
349
|
|
|
@@ -677,8 +681,18 @@ module.exports = class Replicator {
|
|
|
677
681
|
return promise
|
|
678
682
|
}
|
|
679
683
|
|
|
680
|
-
static createRange (start, end, linear) {
|
|
681
|
-
|
|
684
|
+
static createRange (start, end, filter, linear) {
|
|
685
|
+
// createRange(start, end)
|
|
686
|
+
if (filter === undefined) {
|
|
687
|
+
filter = tautology
|
|
688
|
+
|
|
689
|
+
// createRange(start, end, linear)
|
|
690
|
+
} else if (typeof filter === 'boolean') {
|
|
691
|
+
linear = filter
|
|
692
|
+
filter = tautology
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return new Range(start, end, filter, linear)
|
|
682
696
|
}
|
|
683
697
|
|
|
684
698
|
addRange (range) {
|
|
@@ -826,6 +840,10 @@ function pages (core) {
|
|
|
826
840
|
|
|
827
841
|
function noop () {}
|
|
828
842
|
|
|
843
|
+
function tautology () {
|
|
844
|
+
return true
|
|
845
|
+
}
|
|
846
|
+
|
|
829
847
|
function log2 (n) {
|
|
830
848
|
let res = 1
|
|
831
849
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hypercore",
|
|
3
|
-
"version": "10.0.0-alpha.
|
|
3
|
+
"version": "10.0.0-alpha.9",
|
|
4
4
|
"description": "Hypercore 10",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -31,14 +31,15 @@
|
|
|
31
31
|
"@hyperswarm/secret-stream": "^5.0.0",
|
|
32
32
|
"big-sparse-array": "^1.0.2",
|
|
33
33
|
"codecs": "^2.2.0",
|
|
34
|
-
"compact-encoding": "^2.
|
|
34
|
+
"compact-encoding": "^2.5.0",
|
|
35
35
|
"crc32-universal": "^1.0.1",
|
|
36
|
-
"flat-tree": "^1.
|
|
36
|
+
"flat-tree": "^1.9.0",
|
|
37
37
|
"hypercore-crypto": "^2.1.1",
|
|
38
38
|
"is-options": "^1.0.1",
|
|
39
39
|
"random-access-file": "^2.1.4",
|
|
40
40
|
"random-array-iterator": "^1.0.0",
|
|
41
41
|
"safety-catch": "^1.0.1",
|
|
42
|
+
"sodium-universal": "^3.0.4",
|
|
42
43
|
"uint64le": "^1.0.0",
|
|
43
44
|
"xache": "^1.0.0"
|
|
44
45
|
},
|
package/test/basic.js
CHANGED
|
@@ -76,3 +76,15 @@ test('storage options', async function (t) {
|
|
|
76
76
|
t.alike(await core.get(0), Buffer.from('hello'))
|
|
77
77
|
t.end()
|
|
78
78
|
})
|
|
79
|
+
|
|
80
|
+
test(
|
|
81
|
+
'allow publicKeys with different byteLength that 32, if opts.crypto were passed',
|
|
82
|
+
function (t) {
|
|
83
|
+
const key = Buffer.alloc(33).fill('a')
|
|
84
|
+
|
|
85
|
+
const core = new Hypercore(ram, key, { crypto: {} })
|
|
86
|
+
|
|
87
|
+
t.is(core.key, key)
|
|
88
|
+
t.pass('creating a core with more than 32 byteLength key did not throw')
|
|
89
|
+
}
|
|
90
|
+
)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const test = require('brittle')
|
|
2
|
+
const RAM = require('random-access-memory')
|
|
3
|
+
const Hypercore = require('..')
|
|
4
|
+
const { create, replicate } = require('./helpers')
|
|
5
|
+
|
|
6
|
+
const encryptionKey = Buffer.alloc(32, 'hello world')
|
|
7
|
+
|
|
8
|
+
test('encrypted append and get', async function (t) {
|
|
9
|
+
const a = await create({ encryptionKey })
|
|
10
|
+
|
|
11
|
+
t.alike(a.encryptionKey, encryptionKey)
|
|
12
|
+
|
|
13
|
+
await a.append(['hello'])
|
|
14
|
+
|
|
15
|
+
t.is(a.byteLength, 5)
|
|
16
|
+
t.is(a.core.tree.byteLength, 5 + a.padding)
|
|
17
|
+
|
|
18
|
+
const unencrypted = await a.get(0)
|
|
19
|
+
t.alike(unencrypted, Buffer.from('hello'))
|
|
20
|
+
|
|
21
|
+
const encrypted = await a.core.blocks.get(0)
|
|
22
|
+
t.absent(encrypted.includes('hello'))
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('encrypted seek', async function (t) {
|
|
26
|
+
const a = await create({ encryptionKey })
|
|
27
|
+
|
|
28
|
+
await a.append(['hello', 'world', '!'])
|
|
29
|
+
|
|
30
|
+
t.alike(await a.seek(0), [0, 0])
|
|
31
|
+
t.alike(await a.seek(4), [0, 4])
|
|
32
|
+
t.alike(await a.seek(5), [1, 0])
|
|
33
|
+
t.alike(await a.seek(6), [1, 1])
|
|
34
|
+
t.alike(await a.seek(6), [1, 1])
|
|
35
|
+
t.alike(await a.seek(9), [1, 4])
|
|
36
|
+
t.alike(await a.seek(10), [2, 0])
|
|
37
|
+
t.alike(await a.seek(11), [3, 0])
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('encrypted replication', async function (t) {
|
|
41
|
+
const a = await create({ encryptionKey })
|
|
42
|
+
|
|
43
|
+
await a.append(['a', 'b', 'c', 'd', 'e'])
|
|
44
|
+
|
|
45
|
+
t.test('with encryption key', async function (t) {
|
|
46
|
+
t.plan(10)
|
|
47
|
+
|
|
48
|
+
const b = await create(a.key, { encryptionKey })
|
|
49
|
+
|
|
50
|
+
b.on('download', (i, block) => {
|
|
51
|
+
t.alike(block, Buffer.from([i + /* a */ 0x61]))
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
replicate(a, b, t)
|
|
55
|
+
|
|
56
|
+
const r = b.download({ start: 0, end: a.length })
|
|
57
|
+
await r.downloaded()
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < 5; i++) {
|
|
60
|
+
t.alike(await b.get(i), await a.get(i))
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
t.test('without encryption key', async function (t) {
|
|
65
|
+
const b = await create(a.key)
|
|
66
|
+
|
|
67
|
+
replicate(a, b, t)
|
|
68
|
+
|
|
69
|
+
const r = b.download({ start: 0, end: a.length })
|
|
70
|
+
await r.downloaded()
|
|
71
|
+
|
|
72
|
+
for (let i = 0; i < 5; i++) {
|
|
73
|
+
t.unlike(await b.get(i), await a.get(i))
|
|
74
|
+
t.alike(await b.get(i), await a.core.blocks.get(i))
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('encrypted session', async function (t) {
|
|
80
|
+
const a = await create({ encryptionKey })
|
|
81
|
+
|
|
82
|
+
await a.append(['hello'])
|
|
83
|
+
|
|
84
|
+
const s = a.session()
|
|
85
|
+
|
|
86
|
+
t.alike(a.encryptionKey, s.encryptionKey)
|
|
87
|
+
t.alike(await s.get(0), Buffer.from('hello'))
|
|
88
|
+
|
|
89
|
+
await s.append(['world'])
|
|
90
|
+
|
|
91
|
+
const unencrypted = await s.get(1)
|
|
92
|
+
t.alike(unencrypted, Buffer.from('world'))
|
|
93
|
+
t.alike(await a.get(1), unencrypted)
|
|
94
|
+
|
|
95
|
+
const encrypted = await s.core.blocks.get(1)
|
|
96
|
+
t.absent(encrypted.includes('world'))
|
|
97
|
+
t.alike(await a.core.blocks.get(1), encrypted)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('encrypted session before ready core', async function (t) {
|
|
101
|
+
const a = new Hypercore(RAM, { encryptionKey })
|
|
102
|
+
const s = a.session()
|
|
103
|
+
|
|
104
|
+
await a.ready()
|
|
105
|
+
|
|
106
|
+
t.alike(a.encryptionKey, s.encryptionKey)
|
|
107
|
+
|
|
108
|
+
await a.append(['hello'])
|
|
109
|
+
t.alike(await s.get(0), Buffer.from('hello'))
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('replicating encrypted block returns expected data', async function (t) {
|
|
113
|
+
const a = await create({ encryptionKey })
|
|
114
|
+
const b = await create(a.key, { encryptionKey })
|
|
115
|
+
|
|
116
|
+
replicate(a, b, t)
|
|
117
|
+
|
|
118
|
+
await a.append('hej')
|
|
119
|
+
const blk = await b.get(0)
|
|
120
|
+
t.alike(blk, Buffer.from('hej'), 'freshly replicated block is decrypted')
|
|
121
|
+
})
|
package/test/replicate.js
CHANGED
|
@@ -331,3 +331,42 @@ test('destroying a stream and re-replicating works', async function (t) {
|
|
|
331
331
|
|
|
332
332
|
t.is(blocks.length, 33, 'downloaded 33 blocks')
|
|
333
333
|
})
|
|
334
|
+
|
|
335
|
+
test('replicate discrete range', async function (t) {
|
|
336
|
+
const a = await create()
|
|
337
|
+
|
|
338
|
+
await a.append(['a', 'b', 'c', 'd', 'e'])
|
|
339
|
+
|
|
340
|
+
const b = await create(a.key)
|
|
341
|
+
|
|
342
|
+
let d = 0
|
|
343
|
+
b.on('download', () => d++)
|
|
344
|
+
|
|
345
|
+
replicate(a, b, t)
|
|
346
|
+
|
|
347
|
+
const r = b.download({ blocks: [0, 2, 3] })
|
|
348
|
+
await r.downloaded()
|
|
349
|
+
|
|
350
|
+
t.is(d, 3)
|
|
351
|
+
t.alike(await b.get(0), Buffer.from('a'))
|
|
352
|
+
t.alike(await b.get(2), Buffer.from('c'))
|
|
353
|
+
t.alike(await b.get(3), Buffer.from('d'))
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
test('replicate discrete empty range', async function (t) {
|
|
357
|
+
const a = await create()
|
|
358
|
+
|
|
359
|
+
await a.append(['a', 'b', 'c', 'd', 'e'])
|
|
360
|
+
|
|
361
|
+
const b = await create(a.key)
|
|
362
|
+
|
|
363
|
+
let d = 0
|
|
364
|
+
b.on('download', () => d++)
|
|
365
|
+
|
|
366
|
+
replicate(a, b, t)
|
|
367
|
+
|
|
368
|
+
const r = b.download({ blocks: [] })
|
|
369
|
+
await r.downloaded()
|
|
370
|
+
|
|
371
|
+
t.is(d, 0)
|
|
372
|
+
})
|