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/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 = Buffer.from(key, 'hex')
36
+ key = b4a.from(key, 'hex')
32
37
  }
33
- if (key && key.byteLength !== 32) {
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 = opts._opening || this._open(key, storage, opts)
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 lock = name === toLock ? fsctl.lock : null
93
- const sparse = name !== toLock ? fsctl.sparse : null
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._initSession(this)
125
+ s._passCapabilities(this)
122
126
  this.sessions.push(s)
123
127
 
124
128
  return s
125
129
  }
126
130
 
127
- _initSession (o) {
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
- ready () {
216
- return this.opening
312
+ get encryptionKey () {
313
+ return this.encryption && this.encryption.key
217
314
  }
218
315
 
219
- async _open (key, storage, opts) {
220
- if (opts.preload) opts = { ...opts, ...(await opts.preload()) }
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
- for (let i = 0; i < this.sessions.length; i++) {
274
- const s = this.sessions[i]
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) this.sessions[i].emit('truncate', this.core.tree.fork)
284
- if ((status & 0b01) !== 0) this.sessions[i].emit('append')
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, value, from)
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
- if (this.core.bitfield.get(index)) return decode(encoding, await this.core.blocks.get(index))
351
- if (opts && opts.onwait) opts.onwait(index)
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
- return decode(encoding, await this.replicator.requestBlock(index))
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
- // TODO: support range.blocks
430
+ let start
431
+ let end
432
+ let filter
362
433
 
363
- const r = Replicator.createRange(start, end, linear)
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
- const blks = Array.isArray(blocks) ? blocks : [blocks]
397
- const buffers = new Array(blks.length)
481
+ blocks = Array.isArray(blocks) ? blocks : [blocks]
398
482
 
399
- for (let i = 0; i < blks.length; i++) {
400
- const blk = blks[i]
483
+ const preappend = this.encryption && this._preappend
484
+ const buffers = new Array(blocks.length)
401
485
 
402
- const buf = Buffer.isBuffer(blk)
403
- ? blk
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
- function noop () {}
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
- function decode (enc, buf) {
427
- return enc ? c.decode(enc, buf) : buf
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 && buf.toString('hex')
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 b = Buffer.from(page.bitfield.buffer, page.bitfield.byteOffset, page.bitfield.byteLength)
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, b, done)
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
+ }
@@ -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] : Buffer.concat(batch), offset)
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()