hypercore 10.22.2 → 10.23.0

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
@@ -15,6 +15,7 @@ const BlockEncryption = require('./lib/block-encryption')
15
15
  const Info = require('./lib/info')
16
16
  const Download = require('./lib/download')
17
17
  const Batch = require('./lib/batch')
18
+ const { manifestHash, defaultSignerManifest, createVerifier, createManifest, isCompat } = require('./lib/manifest')
18
19
  const { ReadStream, WriteStream, ByteStream } = require('./lib/streams')
19
20
  const {
20
21
  BAD_ARGUMENT,
@@ -69,7 +70,7 @@ module.exports = class Hypercore extends EventEmitter {
69
70
 
70
71
  this.id = null
71
72
  this.key = key || null
72
- this.keyPair = null
73
+ this.keyPair = opts.keyPair || null
73
74
  this.readable = true
74
75
  this.writable = false
75
76
  this.opened = false
@@ -77,22 +78,23 @@ module.exports = class Hypercore extends EventEmitter {
77
78
  this.snapshotted = !!opts.snapshot
78
79
  this.sparse = opts.sparse !== false
79
80
  this.sessions = opts._sessions || [this]
80
- this.auth = opts.auth || null
81
81
  this.autoClose = !!opts.autoClose
82
82
  this.onwait = opts.onwait || null
83
83
  this.wait = opts.wait !== false
84
84
  this.timeout = opts.timeout || 0
85
- this._clone = opts.clone || null
86
- this._readonly = opts.writable === false
87
-
88
85
  this.closing = null
89
- this.opening = this._openSession(key, storage, opts)
90
- this.opening.catch(noop)
86
+ this.opening = null
91
87
 
88
+ this._manifest = opts.manifest || null
89
+ this._clone = opts.clone || null
90
+ this._readonly = opts.writable === false
92
91
  this._preappend = preappend.bind(this)
93
92
  this._snapshot = null
94
93
  this._batch = opts._batch || null
95
94
  this._findingPeers = 0
95
+
96
+ this.opening = this._openSession(key, storage, opts)
97
+ this.opening.catch(noop)
96
98
  }
97
99
 
98
100
  [inspect] (depth, opts) {
@@ -139,6 +141,14 @@ module.exports = class Hypercore extends EventEmitter {
139
141
  indent + ')'
140
142
  }
141
143
 
144
+ static key (manifest, { compat } = {}) {
145
+ return compat ? manifest.signer.publicKey : manifestHash(manifest)
146
+ }
147
+
148
+ static discoveryKey (key) {
149
+ return hypercoreCrypto.discoveryKey(key)
150
+ }
151
+
142
152
  static getProtocolMuxer (stream) {
143
153
  return stream.noiseStream.userData
144
154
  }
@@ -245,20 +255,19 @@ module.exports = class Hypercore extends EventEmitter {
245
255
  }
246
256
 
247
257
  setKeyPair (keyPair) {
248
- this.auth = Core.createAuth(this.crypto, { keyPair })
249
- this.writable = !this._readonly && !!this.auth && !!this.auth.sign
258
+ this.keyPair = keyPair
259
+ this.writable = this._isWritable()
250
260
  }
251
261
 
252
262
  _passCapabilities (o) {
253
- if (!this.auth) this.auth = o.auth
254
-
263
+ if (!this.keyPair) this.keyPair = o.keyPair
255
264
  this.crypto = o.crypto
256
265
  this.id = o.id
257
266
  this.key = o.key
258
267
  this.core = o.core
259
268
  this.replicator = o.replicator
260
269
  this.encryption = o.encryption
261
- this.writable = !this._readonly && !!this.auth && !!this.auth.sign
270
+ this.writable = this._isWritable()
262
271
  this.autoClose = o.autoClose
263
272
 
264
273
  if (this.snapshotted && this.core && !this._snapshot) this._updateSnapshot()
@@ -288,14 +297,6 @@ module.exports = class Hypercore extends EventEmitter {
288
297
  if (!isFirst) await opts._opening
289
298
  if (opts.preload) opts = { ...opts, ...(await this._retryPreload(opts.preload)) }
290
299
 
291
- const keyPair = opts.keyPair
292
-
293
- if (opts.auth) {
294
- this.auth = opts.auth
295
- } else if (keyPair && keyPair.secretKey) {
296
- this.setKeyPair(keyPair)
297
- }
298
-
299
300
  if (isFirst) {
300
301
  await this._openCapabilities(key, storage, opts)
301
302
  // Only the root session should pass capabilities to other sessions.
@@ -306,15 +307,14 @@ module.exports = class Hypercore extends EventEmitter {
306
307
 
307
308
  // copy state over
308
309
  if (this._clone) {
309
- const { from, upgrade } = this._clone
310
+ const { from, signature } = this._clone
310
311
  await from.opening
311
- await this.core.copyFrom(from.core, upgrade)
312
+ await this.core.copyFrom(from.core, signature)
312
313
  this._clone = null
313
314
  }
314
315
  }
315
316
 
316
- if (!this.auth) this.auth = this.core.defaultAuth
317
- this.writable = !this._readonly && !!this.auth && !!this.auth.sign
317
+ this.writable = this._isWritable()
318
318
 
319
319
  if (opts.valueEncoding) {
320
320
  this.valueEncoding = c.from(opts.valueEncoding)
@@ -353,7 +353,7 @@ module.exports = class Hypercore extends EventEmitter {
353
353
  this.storage = Hypercore.defaultStorage(opts.storage || storage, { unlocked, writable: !unlocked })
354
354
 
355
355
  this.core = await Core.open(this.storage, {
356
- compat: opts.compat !== false, // default to true for now
356
+ compat: opts.compat,
357
357
  force: opts.force,
358
358
  createIfMissing: opts.createIfMissing,
359
359
  readonly: unlocked,
@@ -362,7 +362,7 @@ module.exports = class Hypercore extends EventEmitter {
362
362
  keyPair: opts.keyPair,
363
363
  crypto: this.crypto,
364
364
  legacy: opts.legacy,
365
- auth: opts.auth,
365
+ manifest: opts.manifest,
366
366
  onupdate: this._oncoreupdate.bind(this),
367
367
  onconflict: this._oncoreconflict.bind(this)
368
368
  })
@@ -417,6 +417,10 @@ module.exports = class Hypercore extends EventEmitter {
417
417
  return prev.length !== next.length || prev.fork !== next.fork
418
418
  }
419
419
 
420
+ _isWritable () {
421
+ return !this._readonly && !!(this.keyPair && this.keyPair.secretKey)
422
+ }
423
+
420
424
  close (err) {
421
425
  if (this.closing) return this.closing
422
426
  this.closing = this._close(err || null)
@@ -465,23 +469,22 @@ module.exports = class Hypercore extends EventEmitter {
465
469
  this.emit('close', true)
466
470
  }
467
471
 
468
- clone (storage, opts = {}) {
472
+ clone (keyPair, storage, opts = {}) {
469
473
  // TODO: current limitation is no forking
470
474
  if ((opts.fork && opts.fork !== 0) || this.fork !== 0) {
471
475
  throw BAD_ARGUMENT('Cannot clone a fork')
472
476
  }
473
477
 
474
- const key = opts.key === undefined ? opts.keyPair ? null : this.key : opts.key
475
- const keyPair = (opts.auth || opts.keyPair === undefined) ? null : opts.keyPair
478
+ const manifest = opts.manifest || defaultSignerManifest(keyPair.publicKey)
479
+ const key = opts.key || (opts.compat !== false ? manifest.signer.publicKey : manifestHash(manifest))
476
480
 
477
- let auth = this.core.defaultAuth
478
- if (opts.auth) {
479
- auth = opts.auth
480
- } else if (keyPair && keyPair.secretKey) {
481
- auth = Core.createAuth(this.crypto, { keyPair, manifest: { signer: { publicKey: keyPair.publicKey } } })
481
+ if (b4a.equals(key, this.key)) {
482
+ throw BAD_ARGUMENT('Clone cannot share verification information')
482
483
  }
483
484
 
484
- const upgrade = opts.upgrade === undefined ? null : opts.upgrade
485
+ const signature = opts.signature === undefined
486
+ ? createVerifier(createManifest(manifest), { compat: isCompat(key, manifest) }).sign(this.core.tree.batch(), keyPair)
487
+ : opts.signature
485
488
 
486
489
  const sparse = opts.sparse === false ? false : this.sparse
487
490
  const wait = opts.wait === false ? false : this.wait
@@ -497,11 +500,11 @@ module.exports = class Hypercore extends EventEmitter {
497
500
  wait,
498
501
  onwait,
499
502
  timeout,
500
- auth,
503
+ manifest,
501
504
  overwrite: true,
502
505
  clone: {
503
506
  from: this,
504
- upgrade
507
+ signature
505
508
  }
506
509
  })
507
510
  }
@@ -542,6 +545,10 @@ module.exports = class Hypercore extends EventEmitter {
542
545
  return this.replicator === null ? null : this.replicator.discoveryKey
543
546
  }
544
547
 
548
+ get manifest () {
549
+ return this._manifest || (this.core === null ? null : this.core.header.manifest)
550
+ }
551
+
545
552
  get length () {
546
553
  if (this._snapshot) return this._snapshot.length
547
554
  if (this.core === null) return 0
@@ -633,6 +640,13 @@ module.exports = class Hypercore extends EventEmitter {
633
640
  this.replicator.onupgrade()
634
641
  }
635
642
 
643
+ if (status & 0b10000) {
644
+ for (let i = 0; i < this.sessions.length; i++) {
645
+ const s = this.sessions[i]
646
+ if (!s._manifest) s.emit('manifest')
647
+ }
648
+ }
649
+
636
650
  for (let i = 0; i < this.sessions.length; i++) {
637
651
  const s = this.sessions[i]
638
652
 
@@ -930,24 +944,36 @@ module.exports = class Hypercore extends EventEmitter {
930
944
  // Do nothing for now
931
945
  }
932
946
 
933
- async truncate (newLength = 0, fork = -1) {
934
- if (this._batch) throw BATCH_UNFLUSHED()
935
-
947
+ async truncate (newLength = 0, opts = {}) {
936
948
  if (this.opened === false) await this.opening
937
- if (this.writable === false) throw SESSION_NOT_WRITABLE()
938
949
 
939
- if (fork === -1) fork = this.core.tree.fork + 1
940
- await this.core.truncate(newLength, fork, this.auth)
950
+ const {
951
+ fork = this.core.tree.fork + 1,
952
+ force = false,
953
+ keyPair = this.keyPair,
954
+ signature = null
955
+ } = typeof opts === 'number' ? { fork: opts } : opts
956
+
957
+ const writable = !this._readonly && !!(signature || (keyPair && keyPair.secretKey))
958
+
959
+ if (writable === false) throw SESSION_NOT_WRITABLE()
960
+ if (this._batch && !force) throw BATCH_UNFLUSHED()
961
+
962
+ await this.core.truncate(newLength, fork, { keyPair, signature })
941
963
 
942
964
  // TODO: Should propagate from an event triggered by the oplog
943
965
  this.replicator.updateAll()
944
966
  }
945
967
 
946
- async append (blocks, opts) {
968
+ async append (blocks, opts = {}) {
947
969
  if (this._batch && this !== this._batch.session) throw BATCH_UNFLUSHED()
948
970
 
949
971
  if (this.opened === false) await this.opening
950
- if (this.writable === false) throw SESSION_NOT_WRITABLE()
972
+
973
+ const { keyPair = this.keyPair, signature = null } = opts
974
+ const writable = !this._readonly && !!(signature || (keyPair && keyPair.secretKey))
975
+
976
+ if (writable === false) throw SESSION_NOT_WRITABLE()
951
977
 
952
978
  blocks = Array.isArray(blocks) ? blocks : [blocks]
953
979
 
@@ -961,7 +987,7 @@ module.exports = class Hypercore extends EventEmitter {
961
987
  }
962
988
  }
963
989
 
964
- return this.core.append(buffers, (opts && opts.auth) || this.auth, { preappend })
990
+ return this.core.append(buffers, { keyPair, signature, preappend })
965
991
  }
966
992
 
967
993
  async treeHash (length) {
package/lib/batch.js CHANGED
@@ -1,4 +1,4 @@
1
- const { SESSION_NOT_WRITABLE, BLOCK_NOT_AVAILABLE, SESSION_CLOSED } = require('hypercore-errors')
1
+ const { BLOCK_NOT_AVAILABLE, SESSION_CLOSED } = require('hypercore-errors')
2
2
  const EventEmitter = require('events')
3
3
  const c = require('compact-encoding')
4
4
 
@@ -11,11 +11,12 @@ module.exports = class HypercoreBatch extends EventEmitter {
11
11
  this.closed = false
12
12
  this.opening = null
13
13
  this.closing = null
14
+ this.writable = true // always writable...
14
15
  this.autoClose = autoClose
16
+ this.fork = 0
15
17
 
16
18
  this._appends = []
17
19
  this._byteLength = 0
18
- this._fork = 0
19
20
  this._sessionLength = 0
20
21
  this._sessionByteLength = 0
21
22
  this._flushing = null
@@ -51,16 +52,12 @@ module.exports = class HypercoreBatch extends EventEmitter {
51
52
  return this._sessionByteLength + this._byteLength
52
53
  }
53
54
 
54
- get writable () {
55
- return this.session.writable
56
- }
57
-
58
55
  get core () {
59
56
  return this.session.core
60
57
  }
61
58
 
62
- get auth () {
63
- return this.session.auth
59
+ get manifest () {
60
+ return this.session.manifest
64
61
  }
65
62
 
66
63
  async ready () {
@@ -68,6 +65,7 @@ module.exports = class HypercoreBatch extends EventEmitter {
68
65
  if (this.opened) return
69
66
  this._sessionLength = this.session.length
70
67
  this._sessionByteLength = this.session.byteLength
68
+ this.fork = this.session.fork
71
69
  this.opened = true
72
70
  this.emit('ready')
73
71
  }
@@ -169,19 +167,30 @@ module.exports = class HypercoreBatch extends EventEmitter {
169
167
  return b
170
168
  }
171
169
 
172
- async truncate (newLength) {
170
+ async truncate (newLength = 0, opts = {}) {
173
171
  if (this.opened === false) await this.opening
174
- if (this.writable === false) throw SESSION_NOT_WRITABLE()
175
172
  if (this.closing) throw SESSION_CLOSED()
176
173
 
177
174
  // wait for any pending flush... (prop needs a lock)
178
175
  await this._waitForFlush()
179
176
 
177
+ if (typeof opts === 'number') opts = { fork: opts }
178
+ const { fork = this.fork + 1, force = false } = opts
179
+
180
180
  const length = this._sessionLength
181
- if (newLength < length) throw new Error('Cannot truncate committed blocks')
181
+ if (newLength < length) {
182
+ if (!force) throw new Error('Cannot truncate committed blocks')
183
+ this._appends.length = 0
184
+ this._byteLength = 0
185
+ await this.session.truncate(newLength, { fork, force: true, ...opts })
186
+ this._sessionLength = this.session.length
187
+ this._sessionByteLength = this.session.byteLength
188
+ } else {
189
+ for (let i = newLength - length; i < this._appends.length; i++) this._byteLength -= this._appends[i].byteLength
190
+ this._appends.length = newLength - length
191
+ }
182
192
 
183
- this._appends.length = newLength - length
184
- this._fork++
193
+ this.fork = fork
185
194
 
186
195
  this.emit('truncate', newLength, this.fork)
187
196
  }
@@ -190,7 +199,6 @@ module.exports = class HypercoreBatch extends EventEmitter {
190
199
  const session = this.session
191
200
 
192
201
  if (this.opened === false) await this.opening
193
- if (this.writable === false) throw SESSION_NOT_WRITABLE()
194
202
  if (this.closing) throw SESSION_CLOSED()
195
203
 
196
204
  // wait for any pending flush... (prop needs a lock)
@@ -218,12 +226,14 @@ module.exports = class HypercoreBatch extends EventEmitter {
218
226
  return info
219
227
  }
220
228
 
221
- async flush (length = this._appends.length, auth) {
229
+ async flush (opts = {}) {
222
230
  if (this.opened === false) await this.opening
223
231
  if (this.closing) throw SESSION_CLOSED()
224
232
 
233
+ const { length = this._appends.length, keyPair = this.session.keyPair, signature = null } = opts
234
+
225
235
  while (this._flushing) await this._flushing
226
- this._flushing = this._flush(length, auth)
236
+ this._flushing = this._flush(length, keyPair, signature)
227
237
 
228
238
  try {
229
239
  await this._flushing
@@ -234,11 +244,12 @@ module.exports = class HypercoreBatch extends EventEmitter {
234
244
  if (this.autoClose) await this.close()
235
245
  }
236
246
 
237
- async _flush (length, auth) { // TODO: make this safe to interact with a parallel truncate...
247
+ async _flush (length, keyPair, signature) { // TODO: make this safe to interact with a parallel truncate...
238
248
  if (this._appends.length === 0) return
239
249
 
240
250
  const flushingLength = Math.min(length, this._appends.length)
241
- const info = await this.session.append(flushingLength < this._appends.length ? this._appends.slice(0, flushingLength) : this._appends, { auth })
251
+ const blocks = flushingLength < this._appends.length ? this._appends.slice(0, flushingLength) : this._appends
252
+ const info = await this.session.append(blocks, { keyPair, signature })
242
253
  const delta = info.byteLength - this._sessionByteLength
243
254
 
244
255
  this._sessionLength = info.length
@@ -267,7 +278,7 @@ module.exports = class HypercoreBatch extends EventEmitter {
267
278
  _clearAppends () {
268
279
  this._appends = []
269
280
  this._byteLength = 0
270
- this._fork = 0
281
+ this.fork = 0
271
282
  }
272
283
 
273
284
  _clearBatch () {
package/lib/caps.js CHANGED
@@ -8,6 +8,7 @@ const c = require('compact-encoding')
8
8
 
9
9
  const [TREE, REPLICATE_INITIATOR, REPLICATE_RESPONDER, MANIFEST, DEFAULT_NAMESPACE] = crypto.namespace('hypercore', 5)
10
10
 
11
+ exports.MANIFEST = MANIFEST
11
12
  exports.DEFAULT_NAMESPACE = DEFAULT_NAMESPACE
12
13
 
13
14
  exports.replicate = function (isInitiator, key, handshakeHash) {
@@ -16,27 +17,20 @@ exports.replicate = function (isInitiator, key, handshakeHash) {
16
17
  return out
17
18
  }
18
19
 
19
- exports.manifestHash = function (manifest) {
20
- const state = { start: 0, end: 32 + manifest.byteLength, buffer: null }
21
- state.buffer = b4a.allocUnsafe(state.end)
22
- c.raw.encode(state, MANIFEST)
23
- c.raw.encode(state, manifest)
24
- const out = b4a.allocUnsafe(32)
25
- sodium.crypto_generichash(out, state.buffer)
26
- return out
27
- }
28
-
29
- exports.treeSignable = function (hash, length, fork) {
30
- const state = { start: 0, end: 80, buffer: b4a.allocUnsafe(80) }
20
+ exports.treeSignable = function (namespace, hash, length, fork) {
21
+ const state = { start: 0, end: 112, buffer: b4a.allocUnsafe(112) }
31
22
  c.raw.encode(state, TREE)
23
+ c.raw.encode(state, namespace)
32
24
  c.raw.encode(state, hash)
33
25
  c.uint64.encode(state, length)
34
26
  c.uint64.encode(state, fork)
35
27
  return state.buffer
36
28
  }
37
29
 
38
- exports.treeSignableLegacy = function (hash, length, fork) {
39
- const state = { start: 0, end: 48, buffer: b4a.allocUnsafe(48) }
30
+ exports.treeSignableCompat = function (hash, length, fork, noHeader) {
31
+ const end = noHeader ? 48 : 80
32
+ const state = { start: 0, end, buffer: b4a.allocUnsafe(end) }
33
+ if (!noHeader) c.raw.encode(state, TREE) // ultra legacy mode, kill in future major
40
34
  c.raw.encode(state, hash)
41
35
  c.uint64.encode(state, length)
42
36
  c.uint64.encode(state, fork)