hypercore 10.0.0-alpha.3 → 10.0.0-alpha.7

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.
@@ -1,5 +1,4 @@
1
1
  name: Build Status
2
-
3
2
  on:
4
3
  push:
5
4
  branches:
@@ -11,7 +10,7 @@ jobs:
11
10
  build:
12
11
  strategy:
13
12
  matrix:
14
- node-version: [14.x]
13
+ node-version: [lts/*]
15
14
  os: [ubuntu-latest, macos-latest, windows-latest]
16
15
  runs-on: ${{ matrix.os }}
17
16
  steps:
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
@@ -3,6 +3,7 @@ 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 Xache = require('xache')
6
7
  const NoiseSecretStream = require('@hyperswarm/secret-stream')
7
8
  const codecs = require('codecs')
8
9
 
@@ -11,6 +12,7 @@ const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
11
12
  const Replicator = require('./lib/replicator')
12
13
  const Extensions = require('./lib/extensions')
13
14
  const Core = require('./lib/core')
15
+ const BlockEncryption = require('./lib/block-encryption')
14
16
 
15
17
  const promises = Symbol.for('hypercore.promises')
16
18
  const inspect = Symbol.for('nodejs.util.inspect.custom')
@@ -27,14 +29,17 @@ module.exports = class Hypercore extends EventEmitter {
27
29
  opts = key
28
30
  key = null
29
31
  }
32
+
30
33
  if (key && typeof key === 'string') {
31
34
  key = Buffer.from(key, 'hex')
32
35
  }
33
- if (key && key.byteLength !== 32) {
36
+
37
+ if (!opts) opts = {}
38
+
39
+ if (!opts.crypto && key && key.byteLength !== 32) {
34
40
  throw new Error('Hypercore key should be 32 bytes')
35
41
  }
36
42
 
37
- if (!opts) opts = {}
38
43
  if (!storage) storage = opts.storage
39
44
 
40
45
  this[promises] = true
@@ -44,6 +49,8 @@ module.exports = class Hypercore extends EventEmitter {
44
49
  this.core = null
45
50
  this.replicator = null
46
51
  this.extensions = opts.extensions || new Extensions()
52
+ this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
53
+ this.encryption = opts.encryption || null
47
54
 
48
55
  this.valueEncoding = null
49
56
  this.key = key || null
@@ -59,6 +66,8 @@ module.exports = class Hypercore extends EventEmitter {
59
66
  this.closing = null
60
67
  this.opening = opts._opening || this._open(key, storage, opts)
61
68
  this.opening.catch(noop)
69
+
70
+ this._preappend = this.encryption && preappend.bind(this)
62
71
  }
63
72
 
64
73
  [inspect] (depth, opts) {
@@ -114,6 +123,7 @@ module.exports = class Hypercore extends EventEmitter {
114
123
  ...opts,
115
124
  sign: opts.sign || (keyPair && keyPair.secretKey && Core.createSigner(this.crypto, keyPair)) || this.sign,
116
125
  valueEncoding: this.valueEncoding,
126
+ encryption: this.encryption,
117
127
  extensions: this.extensions,
118
128
  _opening: this.opening,
119
129
  _sessions: this.sessions
@@ -202,7 +212,7 @@ module.exports = class Hypercore extends EventEmitter {
202
212
  }
203
213
 
204
214
  get byteLength () {
205
- 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)
206
216
  }
207
217
 
208
218
  get fork () {
@@ -213,6 +223,14 @@ module.exports = class Hypercore extends EventEmitter {
213
223
  return this.replicator === null ? [] : this.replicator.peers
214
224
  }
215
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
+
216
234
  ready () {
217
235
  return this.opening
218
236
  }
@@ -266,6 +284,11 @@ module.exports = class Hypercore extends EventEmitter {
266
284
  this.key = this.core.header.signer.publicKey
267
285
  this.writable = !!this.sign
268
286
 
287
+ if (!this.encryption && opts.encryptionKey) {
288
+ this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
289
+ this._preappend = preappend.bind(this)
290
+ }
291
+
269
292
  this.extensions.attach(this.replicator)
270
293
  this.opened = true
271
294
 
@@ -281,8 +304,13 @@ module.exports = class Hypercore extends EventEmitter {
281
304
  _oncoreupdate (status, bitfield, value, from) {
282
305
  if (status !== 0) {
283
306
  for (let i = 0; i < this.sessions.length; i++) {
284
- if ((status & 0b10) !== 0) this.sessions[i].emit('truncate', this.core.tree.fork)
285
- if ((status & 0b01) !== 0) this.sessions[i].emit('append')
307
+ if ((status & 0b10) !== 0) {
308
+ if (this.cache) this.cache.clear()
309
+ this.sessions[i].emit('truncate', this.core.tree.fork)
310
+ }
311
+ if ((status & 0b01) !== 0) {
312
+ this.sessions[i].emit('append')
313
+ }
286
314
  }
287
315
 
288
316
  this.replicator.broadcastInfo()
@@ -295,6 +323,12 @@ module.exports = class Hypercore extends EventEmitter {
295
323
  }
296
324
 
297
325
  if (value) {
326
+ if (this.encryption) {
327
+ this.encryption.decrypt(bitfield.start, value)
328
+ }
329
+
330
+ value = value.subarray(this.padding)
331
+
298
332
  for (let i = 0; i < this.sessions.length; i++) {
299
333
  this.sessions[i].emit('download', bitfield.start, value, from)
300
334
  }
@@ -333,7 +367,7 @@ module.exports = class Hypercore extends EventEmitter {
333
367
  async seek (bytes) {
334
368
  if (this.opened === false) await this.opening
335
369
 
336
- const s = this.core.tree.seek(bytes)
370
+ const s = this.core.tree.seek(bytes, this.padding)
337
371
 
338
372
  return (await s.update()) || this.replicator.requestSeek(s)
339
373
  }
@@ -346,22 +380,52 @@ module.exports = class Hypercore extends EventEmitter {
346
380
 
347
381
  async get (index, opts) {
348
382
  if (this.opened === false) await this.opening
383
+ const c = this.cache && this.cache.get(index)
384
+ if (c) return c
385
+ const fork = this.core.tree.fork
386
+ const b = await this._get(index, opts)
387
+ if (this.cache && fork === this.core.tree.fork && b) this.cache.set(index, b)
388
+ return b
389
+ }
390
+
391
+ async _get (index, opts) {
349
392
  const encoding = (opts && opts.valueEncoding && c.from(codecs(opts.valueEncoding))) || this.valueEncoding
350
393
 
351
- if (this.core.bitfield.get(index)) return decode(encoding, await this.core.blocks.get(index))
352
- if (opts && opts.onwait) opts.onwait(index)
394
+ let block
395
+
396
+ if (this.core.bitfield.get(index)) {
397
+ block = await this.core.blocks.get(index)
398
+ } else {
399
+ if (opts && opts.onwait) opts.onwait(index)
400
+ block = await this.replicator.requestBlock(index)
401
+ }
353
402
 
354
- return decode(encoding, await this.replicator.requestBlock(index))
403
+ if (this.encryption) this.encryption.decrypt(index, block)
404
+ return this._decode(encoding, block)
355
405
  }
356
406
 
357
407
  download (range) {
358
- const start = (range && range.start) || 0
359
- const end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
360
408
  const linear = !!(range && range.linear)
361
409
 
362
- // TODO: support range.blocks
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
+ }
363
427
 
364
- const r = Replicator.createRange(start, end, linear)
428
+ const r = Replicator.createRange(start, end, filter, linear)
365
429
 
366
430
  if (this.opened) this.replicator.addRange(r)
367
431
  else this.opening.then(() => this.replicator.addRange(r), noop)
@@ -394,22 +458,17 @@ module.exports = class Hypercore extends EventEmitter {
394
458
  if (this.opened === false) await this.opening
395
459
  if (this.writable === false) throw new Error('Core is not writable')
396
460
 
397
- const blks = Array.isArray(blocks) ? blocks : [blocks]
398
- const buffers = new Array(blks.length)
461
+ blocks = Array.isArray(blocks) ? blocks : [blocks]
399
462
 
400
- for (let i = 0; i < blks.length; i++) {
401
- const blk = blks[i]
463
+ const buffers = new Array(blocks.length)
402
464
 
403
- const buf = Buffer.isBuffer(blk)
404
- ? blk
405
- : this.valueEncoding
406
- ? c.encode(this.valueEncoding, blk)
407
- : Buffer.from(blk)
408
-
409
- buffers[i] = buf
465
+ for (let i = 0; i < blocks.length; i++) {
466
+ buffers[i] = this._encode(this.valueEncoding, blocks[i])
410
467
  }
411
468
 
412
- return await this.core.append(buffers, this.sign)
469
+ return await this.core.append(buffers, this.sign, {
470
+ preappend: this._preappend
471
+ })
413
472
  }
414
473
 
415
474
  registerExtension (name, handlers) {
@@ -420,14 +479,38 @@ module.exports = class Hypercore extends EventEmitter {
420
479
  onextensionupdate () {
421
480
  if (this.replicator !== null) this.replicator.broadcastOptions()
422
481
  }
423
- }
424
482
 
425
- function noop () {}
483
+ _encode (enc, val) {
484
+ const state = { start: this.padding, end: this.padding, buffer: null }
485
+
486
+ if (Buffer.isBuffer(val)) {
487
+ if (state.start === 0) return val
488
+ state.end += val.byteLength
489
+ } else if (enc) {
490
+ enc.preencode(state, val)
491
+ } else {
492
+ val = Buffer.from(val)
493
+ if (state.start === 0) return val
494
+ state.end += val.byteLength
495
+ }
496
+
497
+ state.buffer = Buffer.allocUnsafe(state.end)
498
+
499
+ if (enc) enc.encode(state, val)
500
+ else state.buffer.set(val, state.start)
426
501
 
427
- function decode (enc, buf) {
428
- return enc ? c.decode(enc, buf) : buf
502
+ return state.buffer
503
+ }
504
+
505
+ _decode (enc, block) {
506
+ block = block.subarray(this.padding)
507
+ if (enc) return c.decode(enc, block)
508
+ return block
509
+ }
429
510
  }
430
511
 
512
+ function noop () {}
513
+
431
514
  function isStream (s) {
432
515
  return typeof s === 'object' && s && typeof s.pipe === 'function'
433
516
  }
@@ -443,3 +526,25 @@ function requireMaybe (name) {
443
526
  function toHex (buf) {
444
527
  return buf && buf.toString('hex')
445
528
  }
529
+
530
+ function reduce (iter, fn, acc) {
531
+ for (const item of iter) acc = fn(acc, item)
532
+ return acc
533
+ }
534
+
535
+ function min (arr) {
536
+ return reduce(arr, (a, b) => Math.min(a, b), Infinity)
537
+ }
538
+
539
+ function max (arr) {
540
+ return reduce(arr, (a, b) => Math.max(a, b), -Infinity)
541
+ }
542
+
543
+ function preappend (blocks) {
544
+ const offset = this.core.tree.length
545
+ const fork = this.core.tree.fork
546
+
547
+ for (let i = 0; i < blocks.length; i++) {
548
+ this.encryption.encrypt(offset + i, blocks[i], fork)
549
+ }
550
+ }
@@ -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()
@@ -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.start = bytes >= tree.byteLength ? tree.length : 0
279
- this.end = bytes < tree.byteLength ? tree.length : 0
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
- if (bytes === node.size) {
291
- return [flat.rightSpan(node.index) + 2, 0]
292
- }
294
+ let size = node.size
295
+ if (this.padding > 0) size -= this.padding * flat.countLeaves(node.index)
293
296
 
294
- if (bytes > node.size) {
295
- bytes -= node.size
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
- if (l.size === bytes) return [ite.rightSpan() + 2, 0]
305
- if (l.size > bytes) continue
306
- bytes -= l.size
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/protocol.js CHANGED
@@ -125,6 +125,7 @@ class Peer {
125
125
  this.resend = false
126
126
  this.state = state
127
127
  this.extensions = new Map()
128
+ this.destroyed = false
128
129
 
129
130
  this._destroyer = this._safeDestroy.bind(this)
130
131
  }
@@ -229,6 +230,7 @@ class Peer {
229
230
  }
230
231
 
231
232
  destroy (err) {
233
+ this.destroyed = true
232
234
  return this.protocol.unregisterPeer(this, err)
233
235
  }
234
236
  }
@@ -296,7 +298,7 @@ module.exports = class Protocol {
296
298
  peer.remoteAlias = -1
297
299
  }
298
300
 
299
- peer.handlers.onunregister(this, err)
301
+ peer.handlers.onunregister(peer, err)
300
302
 
301
303
  if (err) this.noiseStream.destroy(err)
302
304
  }
package/lib/replicator.js CHANGED
@@ -25,11 +25,12 @@ class InvertedPromise {
25
25
  }
26
26
 
27
27
  class Request {
28
- constructor (index, seek) {
28
+ constructor (index, seek, nodes) {
29
29
  this.peer = null
30
30
  this.index = index
31
31
  this.seek = seek
32
32
  this.value = seek === 0
33
+ this.nodes = nodes
33
34
  this.promises = []
34
35
  }
35
36
 
@@ -119,9 +120,10 @@ class Seek {
119
120
  }
120
121
 
121
122
  class Range {
122
- constructor (start, end, linear) {
123
+ constructor (start, end, filter, linear) {
123
124
  this.start = start
124
125
  this.end = end
126
+ this.filter = filter
125
127
  this.linear = !!linear
126
128
  this.promise = null
127
129
  this.done = false
@@ -144,7 +146,7 @@ class Range {
144
146
  }
145
147
 
146
148
  for (; this._start < this.end; this._start++) {
147
- if (!bitfield.get(this._start)) return false
149
+ if (this.filter(this._start) && !bitfield.get(this._start)) return false
148
150
  }
149
151
 
150
152
  return true
@@ -278,7 +280,9 @@ class RequestPool {
278
280
 
279
281
  _updateSeek (peer, seek) {
280
282
  if (seek.request) return false
281
- seek.request = this._requestRange(peer, seek.seeker.start, seek.seeker.end, seek.seeker.bytes)
283
+ // We have to snapshot the nodes here now, due to the caching of the request...
284
+ const nodes = log2(seek.seeker.end - seek.seeker.start)
285
+ seek.request = this._requestRange(peer, seek.seeker.start, seek.seeker.end, seek.seeker.bytes, nodes)
282
286
  return seek.request !== null
283
287
  }
284
288
 
@@ -296,10 +300,13 @@ class RequestPool {
296
300
  const end = range.end === -1 ? peer.state.length : range.end
297
301
  if (end <= range._start) return false
298
302
 
299
- if (range.linear) return !!this._requestRange(peer, range._start, end, 0)
303
+ if (range.linear) return !!this._requestRange(peer, range._start, end, 0, 0, range.filter)
300
304
 
301
305
  const r = range._start + Math.floor(Math.random() * (end - range._start))
302
- return !!(this._requestRange(peer, r, end, 0) || this._requestRange(peer, range._start, r, 0))
306
+ return !!(
307
+ this._requestRange(peer, r, end, 0, 0, range.filter) ||
308
+ this._requestRange(peer, range._start, r, 0, 0, range.filter)
309
+ )
303
310
  }
304
311
 
305
312
  _updateUpgrade (peer) {
@@ -328,7 +335,7 @@ class RequestPool {
328
335
  this.pendingUpgrade = null
329
336
  }
330
337
 
331
- _requestRange (peer, start, end, seek) {
338
+ _requestRange (peer, start, end, seek, nodes, filter = tautology) {
332
339
  const remote = peer.state.bitfield
333
340
  const local = this.core.bitfield
334
341
 
@@ -336,12 +343,12 @@ class RequestPool {
336
343
  if (end === -1) end = peer.state.length
337
344
 
338
345
  for (let i = start; i < end; i++) {
339
- if (!remote.get(i) || local.get(i)) continue
346
+ if (!filter(i) || !remote.get(i) || local.get(i)) continue
340
347
  // TODO: if this was a NO_VALUE request, retry if no blocks can be found elsewhere
341
348
  if (this.requests.has(i)) continue
342
349
 
343
350
  // TODO: if seeking and i >= core.length, let that takes precendance in the upgrade req
344
- const req = new Request(i, i < this.core.tree.length ? seek : 0)
351
+ const req = new Request(i, i < this.core.tree.length ? seek : 0, nodes)
345
352
  this.requests.set(i, req)
346
353
  this.send(peer, req)
347
354
  return req
@@ -390,16 +397,28 @@ class RequestPool {
390
397
 
391
398
  if (data.block.index < this.core.tree.length || this.core.truncating > 0) {
392
399
  try {
393
- data.block.nodes = await this.core.tree.nodes(data.block.index * 2)
400
+ data.block.nodes = Math.max(req.nodes, await this.core.tree.nodes(data.block.index * 2))
394
401
  } catch (err) {
395
402
  console.error('TODO handle me:', err.stack)
396
403
  }
397
404
  }
398
405
 
406
+ if (peer.destroyed) {
407
+ req.peer = null
408
+ this.pending.push(req)
409
+ if (upgrading) {
410
+ this.upgrading.resolve()
411
+ this.upgrading = null
412
+ }
413
+ this.replicator.updateAll()
414
+ return
415
+ }
416
+
399
417
  if (fork !== this.core.tree.fork || paused(peer, this.core.tree.fork) || this.core.truncating > 0) {
400
418
  if (peer.state.inflight > 0) peer.state.inflight--
401
419
  if (req.promises.length) { // someone is eagerly waiting for this request
402
420
  req.peer = null // resend on some other peer
421
+ this.pending.push(req)
403
422
  } else { // otherwise delete the request
404
423
  this.requests.delete(req.index)
405
424
  }
@@ -594,7 +613,7 @@ class RequestPool {
594
613
  return e.createPromise()
595
614
  }
596
615
 
597
- const r = new Request(index, 0)
616
+ const r = new Request(index, 0, 0)
598
617
 
599
618
  this.requests.set(index, r)
600
619
  this.pending.push(r)
@@ -662,8 +681,18 @@ module.exports = class Replicator {
662
681
  return promise
663
682
  }
664
683
 
665
- static createRange (start, end, linear) {
666
- return new Range(start, end, linear)
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)
667
696
  }
668
697
 
669
698
  addRange (range) {
@@ -810,3 +839,18 @@ function pages (core) {
810
839
  }
811
840
 
812
841
  function noop () {}
842
+
843
+ function tautology () {
844
+ return true
845
+ }
846
+
847
+ function log2 (n) {
848
+ let res = 1
849
+
850
+ while (n > 2) {
851
+ n /= 2
852
+ res++
853
+ }
854
+
855
+ return res
856
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.0.0-alpha.3",
3
+ "version": "10.0.0-alpha.7",
4
4
  "description": "Hypercore 10",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -31,15 +31,17 @@
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.0.0",
34
+ "compact-encoding": "^2.5.0",
35
35
  "crc32-universal": "^1.0.1",
36
- "flat-tree": "^1.7.0",
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
- "uint64le": "^1.0.0"
42
+ "sodium-universal": "^3.0.4",
43
+ "uint64le": "^1.0.0",
44
+ "xache": "^1.0.0"
43
45
  },
44
46
  "devDependencies": {
45
47
  "brittle": "^1.4.1",
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,85 @@
1
+ const test = require('brittle')
2
+ const { create, replicate } = require('./helpers')
3
+
4
+ const encryptionKey = Buffer.alloc(32, 'hello world')
5
+
6
+ test('encrypted append and get', async function (t) {
7
+ const a = await create({ encryptionKey })
8
+
9
+ t.alike(a.encryptionKey, encryptionKey)
10
+
11
+ await a.append(['hello'])
12
+
13
+ t.is(a.byteLength, 5)
14
+ t.is(a.core.tree.byteLength, 5 + a.padding)
15
+
16
+ const unencrypted = await a.get(0)
17
+ const encrypted = await a.core.blocks.get(0)
18
+
19
+ t.alike(unencrypted, Buffer.from('hello'))
20
+ t.unlike(unencrypted, encrypted)
21
+ })
22
+
23
+ test('encrypted seek', async function (t) {
24
+ const a = await create({ encryptionKey })
25
+
26
+ await a.append(['hello', 'world', '!'])
27
+
28
+ t.alike(await a.seek(0), [0, 0])
29
+ t.alike(await a.seek(4), [0, 4])
30
+ t.alike(await a.seek(5), [1, 0])
31
+ t.alike(await a.seek(6), [1, 1])
32
+ t.alike(await a.seek(6), [1, 1])
33
+ t.alike(await a.seek(9), [1, 4])
34
+ t.alike(await a.seek(10), [2, 0])
35
+ t.alike(await a.seek(11), [3, 0])
36
+ })
37
+
38
+ test('encrypted replication', async function (t) {
39
+ const a = await create({ encryptionKey })
40
+
41
+ await a.append(['a', 'b', 'c', 'd', 'e'])
42
+
43
+ t.test('with encryption key', async function (t) {
44
+ t.plan(10)
45
+
46
+ const b = await create(a.key, { encryptionKey })
47
+
48
+ b.on('download', (i, block) => {
49
+ t.alike(block, Buffer.from([i + /* a */ 0x61]))
50
+ })
51
+
52
+ replicate(a, b, t)
53
+
54
+ const r = b.download({ start: 0, end: a.length })
55
+ await r.downloaded()
56
+
57
+ for (let i = 0; i < 5; i++) {
58
+ t.alike(await b.get(i), await a.get(i))
59
+ }
60
+ })
61
+
62
+ t.test('without encryption key', async function (t) {
63
+ const b = await create(a.key)
64
+
65
+ replicate(a, b, t)
66
+
67
+ const r = b.download({ start: 0, end: a.length })
68
+ await r.downloaded()
69
+
70
+ for (let i = 0; i < 5; i++) {
71
+ t.unlike(await b.get(i), await a.get(i))
72
+ t.alike(await b.get(i), await a.core.blocks.get(i))
73
+ }
74
+ })
75
+ })
76
+
77
+ test('encrypted sessions', async function (t) {
78
+ const a = await create({ encryptionKey })
79
+
80
+ await a.append(['hello'])
81
+
82
+ const session = a.session()
83
+
84
+ t.alike(await session.get(0), Buffer.from('hello'))
85
+ })
package/test/replicate.js CHANGED
@@ -294,3 +294,79 @@ test('multiplexing multiple times over the same stream', async function (t) {
294
294
  t.is(b1.length, a1.length, 'same length')
295
295
  t.end()
296
296
  })
297
+
298
+ test('destroying a stream and re-replicating works', async function (t) {
299
+ const core = await create()
300
+
301
+ while (core.length < 33) await core.append(Buffer.from('#' + core.length))
302
+
303
+ const clone = await create(core.key)
304
+
305
+ let s1 = core.replicate(true)
306
+ let s2 = clone.replicate(false)
307
+
308
+ s1.pipe(s2).pipe(s1)
309
+
310
+ await s2.opened
311
+
312
+ const all = []
313
+ for (let i = 0; i < 33; i++) {
314
+ all.push(clone.get(i))
315
+ }
316
+
317
+ clone.once('download', function () {
318
+ // simulate stream failure in the middle of bulk downloading
319
+ s1.destroy()
320
+ })
321
+
322
+ await new Promise((resolve) => s1.once('close', resolve))
323
+
324
+ // retry
325
+ s1 = core.replicate(true)
326
+ s2 = clone.replicate(false)
327
+
328
+ s1.pipe(s2).pipe(s1)
329
+
330
+ const blocks = await Promise.all(all)
331
+
332
+ t.is(blocks.length, 33, 'downloaded 33 blocks')
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
+ })