hypercore 10.20.2 → 10.21.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
@@ -243,6 +243,11 @@ module.exports = class Hypercore extends EventEmitter {
243
243
  return s
244
244
  }
245
245
 
246
+ setKeyPair (keyPair) {
247
+ this.auth = Core.createAuth(this.crypto, { keyPair })
248
+ this.writable = !this._readonly && !!this.auth && !!this.auth.sign
249
+ }
250
+
246
251
  _passCapabilities (o) {
247
252
  if (!this.auth) this.auth = o.auth
248
253
 
@@ -252,7 +257,7 @@ module.exports = class Hypercore extends EventEmitter {
252
257
  this.core = o.core
253
258
  this.replicator = o.replicator
254
259
  this.encryption = o.encryption
255
- this.writable = !this._readonly && !!(this.auth && this.auth.sign)
260
+ this.writable = !this._readonly && !!this.auth && !!this.auth.sign
256
261
  this.autoClose = o.autoClose
257
262
 
258
263
  if (this.snapshotted && this.core && !this._snapshot) this._updateSnapshot()
@@ -282,26 +287,16 @@ module.exports = class Hypercore extends EventEmitter {
282
287
  if (!isFirst) await opts._opening
283
288
  if (opts.preload) opts = { ...opts, ...(await this._retryPreload(opts.preload)) }
284
289
 
285
- const keyPair = (key && opts.keyPair)
286
- ? { ...opts.keyPair, publicKey: key }
287
- : key
288
- ? { publicKey: key, secretKey: null }
289
- : opts.keyPair
290
-
291
- // This only works if the hypercore was fully loaded,
292
- // but we only do this to validate the keypair to help catch bugs so yolo
293
- if (this.key && keyPair) keyPair.publicKey = this.key
290
+ const keyPair = opts.keyPair
294
291
 
295
292
  if (opts.auth) {
296
293
  this.auth = opts.auth
297
- } else if (opts.sign) {
298
- this.auth = Core.createAuth(this.crypto, keyPair, opts)
299
294
  } else if (keyPair && keyPair.secretKey) {
300
- this.auth = Core.createAuth(this.crypto, keyPair)
295
+ this.setKeyPair(keyPair)
301
296
  }
302
297
 
303
298
  if (isFirst) {
304
- await this._openCapabilities(keyPair, storage, opts)
299
+ await this._openCapabilities(key, storage, opts)
305
300
  // Only the root session should pass capabilities to other sessions.
306
301
  for (let i = 0; i < this.sessions.length; i++) {
307
302
  const s = this.sessions[i]
@@ -318,7 +313,7 @@ module.exports = class Hypercore extends EventEmitter {
318
313
  }
319
314
 
320
315
  if (!this.auth) this.auth = this.core.defaultAuth
321
- this.writable = !this._readonly && !!this.auth.sign
316
+ this.writable = !this._readonly && !!this.auth && !!this.auth.sign
322
317
 
323
318
  if (opts.valueEncoding) {
324
319
  this.valueEncoding = c.from(opts.valueEncoding)
@@ -350,18 +345,20 @@ module.exports = class Hypercore extends EventEmitter {
350
345
  }
351
346
  }
352
347
 
353
- async _openCapabilities (keyPair, storage, opts) {
348
+ async _openCapabilities (key, storage, opts) {
354
349
  if (opts.from) return this._openFromExisting(opts.from, opts)
355
350
 
356
351
  const unlocked = !!opts.unlocked
357
352
  this.storage = Hypercore.defaultStorage(opts.storage || storage, { unlocked, writable: !unlocked })
358
353
 
359
354
  this.core = await Core.open(this.storage, {
355
+ compat: opts.compat !== false, // default to true for now
360
356
  force: opts.force,
361
357
  createIfMissing: opts.createIfMissing,
362
358
  readonly: unlocked,
363
359
  overwrite: opts.overwrite,
364
- keyPair,
360
+ key,
361
+ keyPair: opts.keyPair,
365
362
  crypto: this.crypto,
366
363
  legacy: opts.legacy,
367
364
  auth: opts.auth,
@@ -375,8 +372,8 @@ module.exports = class Hypercore extends EventEmitter {
375
372
  }
376
373
  }
377
374
 
378
- this.key = this.core.header.signer.publicKey
379
- this.keyPair = this.core.header.signer
375
+ this.key = this.core.header.key
376
+ this.keyPair = this.core.header.keyPair
380
377
  this.id = z32.encode(this.key)
381
378
 
382
379
  this.replicator = new Replicator(this.core, this.key, {
@@ -404,10 +401,10 @@ module.exports = class Hypercore extends EventEmitter {
404
401
  }
405
402
 
406
403
  return {
407
- length: this.core.header.contiguousLength,
404
+ length: this.core.header.hints.contiguousLength,
408
405
  byteLength: 0,
409
406
  fork: this.core.tree.fork,
410
- compatLength: this.core.header.contiguousLength
407
+ compatLength: this.core.header.hints.contiguousLength
411
408
  }
412
409
  }
413
410
 
@@ -479,13 +476,8 @@ module.exports = class Hypercore extends EventEmitter {
479
476
  let auth = this.core.defaultAuth
480
477
  if (opts.auth) {
481
478
  auth = opts.auth
482
- } else if (opts.sign && keyPair) {
483
- auth = Core.createAuth(this.crypto, keyPair, opts)
484
- } else if (opts.sign) {
485
- // TODO: dangerous to just update sign?
486
- auth.sign = opts.sign
487
479
  } else if (keyPair && keyPair.secretKey) {
488
- auth = Core.createAuth(this.crypto, keyPair)
480
+ auth = Core.createAuth(this.crypto, { keyPair, manifest: { signer: { publicKey: keyPair.publicKey } } })
489
481
  }
490
482
 
491
483
  const upgrade = opts.upgrade === undefined ? null : opts.upgrade
@@ -496,8 +488,10 @@ module.exports = class Hypercore extends EventEmitter {
496
488
  const timeout = opts.timeout === undefined ? this.timeout : opts.timeout
497
489
 
498
490
  const Clz = this.constructor
491
+
499
492
  return new Clz(storage, key, {
500
493
  ...opts,
494
+ keyPair,
501
495
  sparse,
502
496
  wait,
503
497
  onwait,
@@ -569,7 +563,7 @@ module.exports = class Hypercore extends EventEmitter {
569
563
  }
570
564
 
571
565
  get contiguousLength () {
572
- return this.core === null ? 0 : this.core.header.contiguousLength
566
+ return this.core === null ? 0 : this.core.header.hints.contiguousLength
573
567
  }
574
568
 
575
569
  get contiguousByteLength () {
@@ -658,7 +652,7 @@ module.exports = class Hypercore extends EventEmitter {
658
652
  }
659
653
  }
660
654
 
661
- const contig = this.core.header.contiguousLength
655
+ const contig = this.core.header.hints.contiguousLength
662
656
 
663
657
  // When the contig length catches up, broadcast the non-sparse length to peers
664
658
  if (appendedNonSparse && contig === this.core.tree.length) {
@@ -0,0 +1,55 @@
1
+ const c = require('compact-encoding')
2
+ const { oplog } = require('./messages')
3
+
4
+ module.exports = class BigHeader {
5
+ constructor (storage) {
6
+ this.storage = storage
7
+ }
8
+
9
+ async load (external) {
10
+ const buf = await new Promise((resolve, reject) => {
11
+ this.storage.read(external.start, external.length, (err, buf) => {
12
+ if (err) return reject(err)
13
+ resolve(buf)
14
+ })
15
+ })
16
+
17
+ const header = c.decode(oplog.header, buf)
18
+ header.external = external
19
+ return header
20
+ }
21
+
22
+ async flush (header) {
23
+ const external = header.external || { start: 0, length: 0 }
24
+ header.external = null
25
+
26
+ const buf = c.encode(oplog.header, header)
27
+
28
+ let start = 0
29
+ if (buf.byteLength > external.start) {
30
+ start = external.start + external.length
31
+ const rem = start & 4095
32
+ if (rem > 0) start += (4096 - rem)
33
+ }
34
+
35
+ header.external = { start, length: buf.byteLength }
36
+
37
+ await new Promise((resolve, reject) => {
38
+ this.storage.write(start, buf, (err) => {
39
+ if (err) return reject(err)
40
+ resolve()
41
+ })
42
+ })
43
+
44
+ return header
45
+ }
46
+
47
+ close () {
48
+ return new Promise((resolve, reject) => {
49
+ this.storage.close((err) => {
50
+ if (err) return reject(err)
51
+ resolve()
52
+ })
53
+ })
54
+ }
55
+ }
package/lib/caps.js CHANGED
@@ -2,11 +2,14 @@ const crypto = require('hypercore-crypto')
2
2
  const sodium = require('sodium-universal')
3
3
  const b4a = require('b4a')
4
4
  const c = require('compact-encoding')
5
+ const m = require('./messages')
5
6
 
6
7
  // TODO: rename this to "crypto" and move everything hashing related etc in here
7
8
  // Also lets move the tree stuff from hypercore-crypto here
8
9
 
9
- const [TREE, REPLICATE_INITIATOR, REPLICATE_RESPONDER] = crypto.namespace('hypercore', 3)
10
+ const [TREE, REPLICATE_INITIATOR, REPLICATE_RESPONDER, MANIFEST, DEFAULT_NAMESPACE] = crypto.namespace('hypercore', 5)
11
+
12
+ exports.DEFAULT_NAMESPACE = DEFAULT_NAMESPACE
10
13
 
11
14
  exports.replicate = function (isInitiator, key, handshakeHash) {
12
15
  const out = b4a.allocUnsafe(32)
@@ -14,6 +17,17 @@ exports.replicate = function (isInitiator, key, handshakeHash) {
14
17
  return out
15
18
  }
16
19
 
20
+ exports.manifestHash = function (manifest) {
21
+ const state = { start: 0, end: 32, buffer: null }
22
+ m.manifest.preencode(state, manifest)
23
+ state.buffer = b4a.allocUnsafe(state.end)
24
+ c.raw.encode(state, MANIFEST)
25
+ m.manifest.encode(state, manifest)
26
+ const out = b4a.allocUnsafe(32)
27
+ sodium.crypto_generichash(out, state.buffer)
28
+ return out
29
+ }
30
+
17
31
  exports.treeSignable = function (hash, length, fork) {
18
32
  const state = { start: 0, end: 80, buffer: b4a.allocUnsafe(80) }
19
33
  c.raw.encode(state, TREE)
package/lib/core.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const hypercoreCrypto = require('hypercore-crypto')
2
2
  const b4a = require('b4a')
3
3
  const Oplog = require('./oplog')
4
+ const BigHeader = require('./big-header')
4
5
  const Mutex = require('./mutex')
5
6
  const MerkleTree = require('./merkle-tree')
6
7
  const BlockStore = require('./block-store')
@@ -8,15 +9,18 @@ const Bitfield = require('./bitfield')
8
9
  const Info = require('./info')
9
10
  const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_SIGNATURE } = require('hypercore-errors')
10
11
  const m = require('./messages')
12
+ const caps = require('./caps')
11
13
 
12
14
  module.exports = class Core {
13
- constructor (header, crypto, oplog, tree, blocks, bitfield, auth, legacy, onupdate, onconflict) {
15
+ constructor (header, crypto, oplog, bigHeader, tree, blocks, bitfield, auth, legacy, onupdate, onconflict) {
14
16
  this.onupdate = onupdate
15
17
  this.onconflict = onconflict
16
18
  this.preupdate = null
17
19
  this.header = header
20
+ this.compat = !!(header.manifest && header.manifest.signer && b4a.equals(header.key, header.manifest.signer.publicKey))
18
21
  this.crypto = crypto
19
22
  this.oplog = oplog
23
+ this.bigHeader = bigHeader
20
24
  this.tree = tree
21
25
  this.blocks = blocks
22
26
  this.bitfield = bitfield
@@ -24,6 +28,7 @@ module.exports = class Core {
24
28
  this.truncating = 0
25
29
  this.closed = false
26
30
 
31
+ this._manifestFlushed = !!header.manifest
27
32
  this._maxOplogSize = 65536
28
33
  this._autoFlush = 1
29
34
  this._verifies = null
@@ -37,35 +42,31 @@ module.exports = class Core {
37
42
  const treeFile = storage('tree')
38
43
  const bitfieldFile = storage('bitfield')
39
44
  const dataFile = storage('data')
45
+ const headerFile = storage('header')
40
46
 
41
47
  try {
42
- return await this.resume(oplogFile, treeFile, bitfieldFile, dataFile, opts)
48
+ return await this.resume(oplogFile, treeFile, bitfieldFile, dataFile, headerFile, opts)
43
49
  } catch (err) {
44
- await closeAll(oplogFile, treeFile, bitfieldFile, dataFile)
50
+ await closeAll(oplogFile, treeFile, bitfieldFile, dataFile, headerFile)
45
51
  throw err
46
52
  }
47
53
  }
48
54
 
49
- static createAuth (crypto, { publicKey, secretKey }, opts = {}) {
50
- if (secretKey && !crypto.validateKeyPair({ publicKey, secretKey })) {
51
- throw BAD_ARGUMENT('Invalid key pair')
52
- }
53
-
54
- const sign = opts.sign
55
- ? opts.sign
56
- : secretKey
57
- ? (signable) => crypto.sign(signable, secretKey)
58
- : undefined
55
+ static createAuth (crypto, header) {
56
+ const manifest = header.manifest || defaultSignerManifest(header.keyPair.publicKey)
57
+ const secretKey = header.keyPair && header.keyPair.secretKey
58
+ const publicKey = manifest.signer.publicKey
59
+ const sign = signable => crypto.sign(signable, secretKey)
59
60
 
60
61
  return {
61
- sign,
62
+ sign: secretKey ? sign : null,
62
63
  verify (signable, signature) {
63
64
  return crypto.verify(signable, signature, publicKey)
64
65
  }
65
66
  }
66
67
  }
67
68
 
68
- static async resume (oplogFile, treeFile, bitfieldFile, dataFile, opts) {
69
+ static async resume (oplogFile, treeFile, bitfieldFile, dataFile, headerFile, opts) {
69
70
  let overwrite = opts.overwrite === true
70
71
 
71
72
  const force = opts.force === true
@@ -80,17 +81,31 @@ module.exports = class Core {
80
81
 
81
82
  let { header, entries } = await oplog.open()
82
83
 
83
- if (force && opts.keyPair && header && header.signer && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
84
+ if (force && opts.key && header && !b4a.equals(header.key, opts.key)) {
84
85
  overwrite = true
85
86
  }
86
87
 
88
+ const bigHeader = new BigHeader(headerFile)
89
+
87
90
  if (!header || overwrite) {
88
91
  if (!createIfMissing) {
89
92
  throw STORAGE_EMPTY('No Hypercore is stored here')
90
93
  }
91
94
 
95
+ if (opts.compat) {
96
+ if (opts.key && opts.keyPair && !b4a.equals(opts.key, opts.keyPair.publicKey)) {
97
+ throw BAD_ARGUMENT('Key must match publicKey when in compat mode.')
98
+ }
99
+ }
100
+
101
+ const keyPair = opts.keyPair || (opts.key ? null : crypto.keyPair())
102
+ const manifest = opts.manifest || (opts.key && !opts.compat) ? null : defaultSignerManifest(opts.key || keyPair.publicKey)
103
+
92
104
  header = {
93
- types: { tree: 'blake2b', bitfield: 'raw', signer: 'ed25519' },
105
+ external: null,
106
+ key: opts.key || (opts.compat ? manifest.signer.publicKey : caps.manifestHash(manifest)),
107
+ manifest,
108
+ keyPair,
94
109
  userData: [],
95
110
  tree: {
96
111
  fork: 0,
@@ -98,17 +113,18 @@ module.exports = class Core {
98
113
  rootHash: null,
99
114
  signature: null
100
115
  },
101
- signer: opts.keyPair || crypto.keyPair(),
102
116
  hints: {
103
- reorgs: []
104
- },
105
- contiguousLength: 0
117
+ reorgs: [],
118
+ contiguousLength: 0
119
+ }
106
120
  }
107
121
 
108
- await oplog.flush(header)
122
+ await flushHeader(oplog, bigHeader, header)
123
+ } else if (header.external) {
124
+ header = await bigHeader.load(header.external)
109
125
  }
110
126
 
111
- if (opts.keyPair && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
127
+ if (opts.key && !b4a.equals(header.key, opts.key)) {
112
128
  throw STORAGE_CONFLICT('Another Hypercore is stored here')
113
129
  }
114
130
 
@@ -127,11 +143,11 @@ module.exports = class Core {
127
143
  }
128
144
 
129
145
  // compat from earlier version that do not store contig length
130
- if (header.contiguousLength === 0) {
131
- while (bitfield.get(header.contiguousLength)) header.contiguousLength++
146
+ if (header.hints.contiguousLength === 0) {
147
+ while (bitfield.get(header.hints.contiguousLength)) header.hints.contiguousLength++
132
148
  }
133
149
 
134
- const auth = opts.auth || this.createAuth(crypto, header.signer)
150
+ const auth = opts.auth || (header.manifest ? this.createAuth(crypto, header) : null)
135
151
 
136
152
  for (const e of entries) {
137
153
  if (e.userData) {
@@ -163,7 +179,7 @@ module.exports = class Core {
163
179
  }
164
180
  }
165
181
 
166
- return new this(header, crypto, oplog, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop, opts.onconflict || noop)
182
+ return new this(header, crypto, oplog, bigHeader, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop, opts.onconflict || noop)
167
183
  }
168
184
 
169
185
  _shouldFlush () {
@@ -173,6 +189,11 @@ module.exports = class Core {
173
189
  return true
174
190
  }
175
191
 
192
+ if (!this._manifestFlushed && this.header.manifest) {
193
+ this._manifestFlushed = true
194
+ return true
195
+ }
196
+
176
197
  return false
177
198
  }
178
199
 
@@ -223,7 +244,7 @@ module.exports = class Core {
223
244
 
224
245
  this.tree.signature = signature || auth.sign(this.tree.signable())
225
246
 
226
- if (signature && !this._verifyBatch(this.tree)) {
247
+ if (signature && !this._verifyBatchUpgrade(this.tree, null)) {
227
248
  // TODO: how to handle signature failure?
228
249
  this.tree.signature = null
229
250
  throw INVALID_SIGNATURE('Clone was provided with an invalid signature')
@@ -246,7 +267,8 @@ module.exports = class Core {
246
267
  // background. Might be easier to impl that where it is called instead and keep this one simple.
247
268
  await this.bitfield.flush()
248
269
  await this.tree.flush()
249
- await this.oplog.flush(this.header)
270
+
271
+ return flushHeader(this.oplog, this.bigHeader, this.header)
250
272
  }
251
273
 
252
274
  _appendBlocks (values) {
@@ -325,8 +347,8 @@ module.exports = class Core {
325
347
 
326
348
  this.bitfield.setRange(start, end - start, false)
327
349
 
328
- if (start < this.header.contiguousLength) {
329
- this.header.contiguousLength = start
350
+ if (start < this.header.hints.contiguousLength) {
351
+ this.header.hints.contiguousLength = start
330
352
  }
331
353
 
332
354
  start = this.bitfield.lastSet(start) + 1
@@ -423,18 +445,33 @@ module.exports = class Core {
423
445
  }
424
446
  }
425
447
 
426
- _verifyBatch (batch) {
448
+ _verifyBatchUpgrade (batch, manifest) {
427
449
  const hash = batch.hash()
428
450
  const signable = this._legacy ? batch.signableLegacy(hash) : batch.signable(hash)
429
- const auth = this.defaultAuth
451
+
452
+ if (!this.header.manifest) {
453
+ if (!manifest) { // compat mode, remove in future version
454
+ manifest = defaultSignerManifest(this.header.key)
455
+ } else if (!manifest || !b4a.equals(this.header.key, caps.manifestHash(manifest))) {
456
+ throw INVALID_SIGNATURE('Proof contains an invalid manifest') // TODO: proper error type
457
+ }
458
+ }
459
+
460
+ const auth = this.defaultAuth || Core.createAuth(this.crypto, { ...this.header, manifest })
430
461
 
431
462
  if (!batch.signature || !auth.verify(signable, batch.signature, batch)) {
432
463
  throw INVALID_SIGNATURE('Proof contains an invalid signature')
433
464
  }
465
+
466
+ this.defaultAuth = auth
467
+ if (!this.header.manifest) {
468
+ this.compat = !!manifest.signer && b4a.equals(this.header.key, manifest.signer.publicKey)
469
+ this.header.manifest = manifest
470
+ }
434
471
  }
435
472
 
436
- async _verifyExclusive ({ batch, bitfield, value, from }) {
437
- this._verifyBatch(batch)
473
+ async _verifyExclusive ({ batch, bitfield, value, manifest, from }) {
474
+ this._verifyBatchUpgrade(batch, manifest)
438
475
 
439
476
  await this._mutex.lock()
440
477
 
@@ -448,7 +485,7 @@ module.exports = class Core {
448
485
  bitfield
449
486
  }
450
487
 
451
- if (this.preupdate !== null) await this.preupdate(batch, this.header.signer.publicKey)
488
+ if (this.preupdate !== null) await this.preupdate(batch, this.header.key)
452
489
  if (bitfield) await this._writeBlock(batch, bitfield.start, value)
453
490
 
454
491
  await this.oplog.append([entry], false)
@@ -464,7 +501,7 @@ module.exports = class Core {
464
501
 
465
502
  this.header.tree.fork = batch.fork
466
503
  this.header.tree.length = batch.length
467
- this.header.tree.rootHash = batch.rootHash
504
+ this.header.tree.rootHash = batch.hash()
468
505
  this.header.tree.signature = batch.signature
469
506
 
470
507
  this.onupdate(status, bitfield, value, from)
@@ -542,7 +579,7 @@ module.exports = class Core {
542
579
 
543
580
  const batch = this.tree.verifyFullyRemote(proof)
544
581
 
545
- this._verifyBatch(batch)
582
+ this._verifyBatchUpgrade(batch, proof.manifest)
546
583
 
547
584
  const remoteTreeHash = this.crypto.tree(proof.upgrade.nodes)
548
585
  const localTreeHash = this.crypto.tree(await this.tree.getRoots(proof.upgrade.length))
@@ -556,7 +593,7 @@ module.exports = class Core {
556
593
  async verifyReorg (proof) {
557
594
  const batch = await this.tree.reorg(proof)
558
595
 
559
- this._verifyBatch(batch)
596
+ this._verifyBatchUpgrade(batch, proof.manifest)
560
597
 
561
598
  return batch
562
599
  }
@@ -565,7 +602,6 @@ module.exports = class Core {
565
602
  // We cannot apply "other forks" atm.
566
603
  // We should probably still try and they are likely super similar for non upgrades
567
604
  // but this is easy atm (and the above layer will just retry)
568
-
569
605
  if (proof.fork !== this.tree.fork) return false
570
606
 
571
607
  const batch = await this.tree.verify(proof)
@@ -576,6 +612,7 @@ module.exports = class Core {
576
612
  batch,
577
613
  bitfield: value && { drop: false, start: proof.block.index, length: 1 },
578
614
  value,
615
+ manifest: proof.manifest,
579
616
  from
580
617
  }
581
618
 
@@ -653,7 +690,8 @@ module.exports = class Core {
653
690
  this.oplog.close(),
654
691
  this.bitfield.close(),
655
692
  this.tree.close(),
656
- this.blocks.close()
693
+ this.blocks.close(),
694
+ this.bigHeader.close()
657
695
  ])
658
696
  }
659
697
  }
@@ -661,7 +699,7 @@ module.exports = class Core {
661
699
  function updateContig (header, upd, bitfield) {
662
700
  const end = upd.start + upd.length
663
701
 
664
- let c = header.contiguousLength
702
+ let c = header.hints.contiguousLength
665
703
 
666
704
  if (upd.drop) {
667
705
  // If we dropped a block in the current contig range, "downgrade" it
@@ -675,16 +713,16 @@ function updateContig (header, upd, bitfield) {
675
713
  }
676
714
  }
677
715
 
678
- if (c === header.contiguousLength) {
716
+ if (c === header.hints.contiguousLength) {
679
717
  return 0b0000
680
718
  }
681
719
 
682
- if (c > header.contiguousLength) {
683
- header.contiguousLength = c
720
+ if (c > header.hints.contiguousLength) {
721
+ header.hints.contiguousLength = c
684
722
  return 0b0100
685
723
  }
686
724
 
687
- header.contiguousLength = c
725
+ header.hints.contiguousLength = c
688
726
  return 0b1000
689
727
  }
690
728
 
@@ -732,4 +770,31 @@ function closeAll (...storages) {
732
770
  })
733
771
  }
734
772
 
773
+ async function flushHeader (oplog, bigHeader, header) {
774
+ if (header.external) {
775
+ await bigHeader.flush(header)
776
+ }
777
+
778
+ try {
779
+ await oplog.flush(header)
780
+ } catch (err) {
781
+ if (err.code !== 'OPLOG_HEADER_OVERFLOW') throw err
782
+ await bigHeader.flush(header)
783
+ await oplog.flush(header)
784
+ }
785
+ }
786
+
787
+ function defaultSignerManifest (publicKey) {
788
+ return {
789
+ hash: 'blake2b',
790
+ static: null,
791
+ signer: {
792
+ signature: 'ed25519',
793
+ namespace: caps.DEFAULT_NAMESPACE,
794
+ publicKey
795
+ },
796
+ multipleSigners: null
797
+ }
798
+ }
799
+
735
800
  function noop () {}
@@ -48,6 +48,7 @@ class MerkleTreeBatch {
48
48
  this.ancestors = tree.length
49
49
  this.byteLength = tree.byteLength
50
50
  this.signature = null
51
+ this.hashCached = null
51
52
 
52
53
  this.treeLength = tree.length
53
54
  this.treeFork = tree.fork
@@ -74,7 +75,8 @@ class MerkleTreeBatch {
74
75
  }
75
76
 
76
77
  hash () {
77
- return this.tree.crypto.tree(this.roots)
78
+ if (this.hashCached === null) this.hashCached = this.tree.crypto.tree(this.roots)
79
+ return this.hashCached
78
80
  }
79
81
 
80
82
  signable (hash = this.hash()) {
@@ -122,6 +124,7 @@ class MerkleTreeBatch {
122
124
  }
123
125
 
124
126
  appendRoot (node, ite) {
127
+ this.hashCached = null
125
128
  this.upgraded = true
126
129
  this.length += ite.factor / 2
127
130
  this.byteLength += node.size
@@ -1215,7 +1218,7 @@ async function generateProof (tree, block, hash, seek, upgrade) {
1215
1218
  }
1216
1219
 
1217
1220
  const [pNode, pSeek, pUpgrade, pAdditional] = await settleProof(p)
1218
- const result = { fork, block: null, hash: null, seek: null, upgrade: null }
1221
+ const result = { fork, block: null, hash: null, seek: null, upgrade: null, manifest: null }
1219
1222
 
1220
1223
  if (block) {
1221
1224
  result.block = {
package/lib/messages.js CHANGED
@@ -4,6 +4,140 @@ const { INVALID_OPLOG_VERSION } = require('hypercore-errors')
4
4
 
5
5
  const EMPTY = b4a.alloc(0)
6
6
 
7
+ const hashes = {
8
+ preencode (state, m) {
9
+ state.end++ // small uint
10
+ },
11
+ encode (state, m) {
12
+ if (m === 'blake2b') {
13
+ c.uint.encode(state, 0)
14
+ return
15
+ }
16
+
17
+ throw new Error('Unknown hash: ' + m)
18
+ },
19
+ decode (state) {
20
+ const n = c.uint.decode(state)
21
+ if (n === 0) return 'blake2b'
22
+ throw new Error('Unknown hash id: ' + n)
23
+ }
24
+ }
25
+
26
+ const signatures = {
27
+ preencode (state, m) {
28
+ state.end++ // small uint
29
+ },
30
+ encode (state, m) {
31
+ if (m === 'ed25519') {
32
+ c.uint.encode(state, 0)
33
+ return
34
+ }
35
+
36
+ throw new Error('Unknown signature: ' + m)
37
+ },
38
+ decode (state) {
39
+ const n = c.uint.decode(state)
40
+ if (n === 0) return 'ed25519'
41
+ throw new Error('Unknown signature id: ' + n)
42
+ }
43
+ }
44
+
45
+ const signer = {
46
+ preencode (state, m) {
47
+ signatures.preencode(state, m.signature)
48
+ c.fixed32.preencode(state, m.namespace)
49
+ c.fixed32.preencode(state, m.publicKey)
50
+ },
51
+ encode (state, m) {
52
+ signatures.encode(state, m.signature)
53
+ c.fixed32.encode(state, m.namespace)
54
+ c.fixed32.encode(state, m.publicKey)
55
+ },
56
+ decode (state) {
57
+ return {
58
+ signature: signatures.decode(state),
59
+ namespace: c.fixed32.decode(state),
60
+ publicKey: c.fixed32.decode(state)
61
+ }
62
+ }
63
+ }
64
+
65
+ const signerArray = c.array(signer)
66
+
67
+ const multipleSigners = {
68
+ preencode (state, m) {
69
+ state.end++ // flags
70
+ c.uint.preencode(state, m.quorum)
71
+ signerArray.preencode(state, m.signers)
72
+ },
73
+ encode (state, m) {
74
+ c.uint.encode(state, m.allowPatched ? 1 : 0)
75
+ c.uint.encode(state, m.quorum)
76
+ signerArray.encode(state, m.signers)
77
+ },
78
+ decode (state) {
79
+ const flags = c.uint.decode(state)
80
+ return {
81
+ allowPatched: (flags & 1) !== 0,
82
+ quorum: c.uint.decode(state),
83
+ signers: signerArray.decode(state)
84
+ }
85
+ }
86
+ }
87
+
88
+ const manifest = exports.manifest = {
89
+ preencode (state, m) {
90
+ c.uint.preencode(state, 0) // version
91
+ hashes.preencode(state, m.hash)
92
+ c.uint.preencode(state, 2) // type
93
+
94
+ if (m.static) {
95
+ c.fixed32.preencode(state, m.static)
96
+ }
97
+
98
+ if (m.signer) {
99
+ signer.preencode(state, m.signer)
100
+ }
101
+
102
+ if (m.multipleSigners) {
103
+ multipleSigners.preencode(state, m.multipleSigners)
104
+ }
105
+ },
106
+ encode (state, m) {
107
+ c.uint.encode(state, 0) // version
108
+ hashes.encode(state, m.hash)
109
+ c.uint.encode(state, m.signer ? 1 : m.multipleSigners ? 2 : 0)
110
+
111
+ if (m.static) {
112
+ c.fixed32.encode(state, m.static)
113
+ }
114
+
115
+ if (m.signer) {
116
+ signer.encode(state, m.signer)
117
+ }
118
+
119
+ if (m.multipleSigners) {
120
+ multipleSigners.encode(state, m.multipleSigners)
121
+ }
122
+ },
123
+ decode (state) {
124
+ const version = c.uint.decode(state)
125
+ if (version !== 0) throw new Error('Invalid version: ' + version)
126
+
127
+ const hash = hashes.decode(state)
128
+ const type = c.uint.decode(state)
129
+
130
+ if (type > 2) throw new Error('Unknown type: ' + type)
131
+
132
+ return {
133
+ hash,
134
+ static: type === 0 ? c.fixed32.decode(state) : null,
135
+ signer: type === 1 ? signer.decode(state) : null,
136
+ multipleSigners: type === 2 ? multipleSigners.decode(state) : null
137
+ }
138
+ }
139
+ }
140
+
7
141
  const node = {
8
142
  preencode (state, n) {
9
143
  c.uint.preencode(state, n.index)
@@ -30,16 +164,17 @@ const wire = exports.wire = {}
30
164
 
31
165
  wire.handshake = {
32
166
  preencode (state, m) {
33
- c.uint.preencode(state, 0) // flags for the future
167
+ c.uint.preencode(state, 0)
34
168
  c.fixed32.preencode(state, m.capability)
35
169
  },
36
170
  encode (state, m) {
37
- c.uint.encode(state, 0) // flags for the future
171
+ c.uint.encode(state, m.manifest ? 1 : 0) // flags for the future
38
172
  c.fixed32.encode(state, m.capability)
39
173
  },
40
174
  decode (state) {
41
- c.uint.decode(state) // flags for the future
175
+ const flags = c.uint.decode(state)
42
176
  return {
177
+ manifest: (flags & 1) !== 0,
43
178
  capability: c.fixed32.decode(state)
44
179
  }
45
180
  }
@@ -106,7 +241,7 @@ wire.request = {
106
241
  if (m.priority) c.uint.preencode(state, m.priority)
107
242
  },
108
243
  encode (state, m) {
109
- const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0) | (m.priority ? 16 : 0)
244
+ const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0) | (m.manifest ? 16 : 0) | (m.priority ? 32 : 0)
110
245
 
111
246
  c.uint.encode(state, flags)
112
247
  c.uint.encode(state, m.id)
@@ -128,7 +263,8 @@ wire.request = {
128
263
  hash: flags & 2 ? requestBlock.decode(state) : null,
129
264
  seek: flags & 4 ? requestSeek.decode(state) : null,
130
265
  upgrade: flags & 8 ? requestUpgrade.decode(state) : null,
131
- priority: flags & 16 ? c.uint.decode(state) : 0
266
+ manifest: (flags & 16) !== 0,
267
+ priority: flags & 32 ? c.uint.decode(state) : 0
132
268
  }
133
269
  }
134
270
  }
@@ -237,9 +373,10 @@ wire.data = {
237
373
  if (m.hash) dataHash.preencode(state, m.hash)
238
374
  if (m.seek) dataSeek.preencode(state, m.seek)
239
375
  if (m.upgrade) dataUpgrade.preencode(state, m.upgrade)
376
+ if (m.manifest) manifest.preencode(state, m.manifest)
240
377
  },
241
378
  encode (state, m) {
242
- const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0)
379
+ const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0) | (m.manifest ? 16 : 0)
243
380
 
244
381
  c.uint.encode(state, flags)
245
382
  c.uint.encode(state, m.request)
@@ -249,6 +386,7 @@ wire.data = {
249
386
  if (m.hash) dataHash.encode(state, m.hash)
250
387
  if (m.seek) dataSeek.encode(state, m.seek)
251
388
  if (m.upgrade) dataUpgrade.encode(state, m.upgrade)
389
+ if (m.manifest) manifest.encode(state, m.manifest)
252
390
  },
253
391
  decode (state) {
254
392
  const flags = c.uint.decode(state)
@@ -259,7 +397,8 @@ wire.data = {
259
397
  block: flags & 1 ? dataBlock.decode(state) : null,
260
398
  hash: flags & 2 ? dataHash.decode(state) : null,
261
399
  seek: flags & 4 ? dataSeek.decode(state) : null,
262
- upgrade: flags & 8 ? dataUpgrade.decode(state) : null
400
+ upgrade: flags & 8 ? dataUpgrade.decode(state) : null,
401
+ manifest: flags & 16 ? manifest.decode(state) : null
263
402
  }
264
403
  }
265
404
  }
@@ -562,13 +701,16 @@ const reorgHintArray = c.array(reorgHint)
562
701
  const hints = {
563
702
  preencode (state, h) {
564
703
  reorgHintArray.preencode(state, h.reorgs)
704
+ c.uint.preencode(state, h.contiguousLength)
565
705
  },
566
706
  encode (state, h) {
567
707
  reorgHintArray.encode(state, h.reorgs)
708
+ c.uint.encode(state, h.contiguousLength)
568
709
  },
569
710
  decode (state) {
570
711
  return {
571
- reorgs: reorgHintArray.decode(state)
712
+ reorgs: reorgHintArray.decode(state),
713
+ contiguousLength: state.start < state.end ? c.uint.decode(state) : 0
572
714
  }
573
715
  }
574
716
  }
@@ -616,41 +758,112 @@ const types = {
616
758
  }
617
759
  }
618
760
 
761
+ const externalHeader = {
762
+ preencode (state, m) {
763
+ c.uint.preencode(state, m.start)
764
+ c.uint.preencode(state, m.length)
765
+ },
766
+ encode (state, m) {
767
+ c.uint.encode(state, m.start)
768
+ c.uint.encode(state, m.length)
769
+ },
770
+ decode (state) {
771
+ return {
772
+ start: c.uint.decode(state),
773
+ length: c.uint.decode(state)
774
+ }
775
+ }
776
+ }
777
+
619
778
  const keyValueArray = c.array(keyValue)
620
779
 
621
780
  oplog.header = {
622
781
  preencode (state, h) {
623
- state.end += 1 // version
624
- types.preencode(state, h.types)
782
+ state.end += 2 // version + flags
783
+ if (h.external) {
784
+ externalHeader.preencode(state, h.external)
785
+ return
786
+ }
787
+ c.fixed32.preencode(state, h.key)
788
+ if (h.manifest) manifest.preencode(state, h.manifest)
789
+ if (h.keyPair) keyPair.preencode(state, h.keyPair)
625
790
  keyValueArray.preencode(state, h.userData)
626
791
  treeHeader.preencode(state, h.tree)
627
- keyPair.preencode(state, h.signer)
628
792
  hints.preencode(state, h.hints)
629
- c.uint.preencode(state, h.contiguousLength)
630
793
  },
631
794
  encode (state, h) {
632
- state.buffer[state.start++] = 0 // version
633
- types.encode(state, h.types)
795
+ c.uint.encode(state, 1)
796
+ if (h.external) {
797
+ c.uint.encode(state, 1) // ONLY set the first big for clarity
798
+ externalHeader.encode(state, h.external)
799
+ return
800
+ }
801
+ c.uint.encode(state, (h.manifest ? 2 : 0) | (h.keyPair ? 4 : 0))
802
+ c.fixed32.encode(state, h.key)
803
+ if (h.manifest) manifest.encode(state, h.manifest)
804
+ if (h.keyPair) keyPair.encode(state, h.keyPair)
634
805
  keyValueArray.encode(state, h.userData)
635
806
  treeHeader.encode(state, h.tree)
636
- keyPair.encode(state, h.signer)
637
807
  hints.encode(state, h.hints)
638
- c.uint.encode(state, h.contiguousLength)
639
808
  },
640
809
  decode (state) {
641
810
  const version = c.uint.decode(state)
642
811
 
643
- if (version !== 0) {
644
- throw INVALID_OPLOG_VERSION('Invalid header version. Expected 0, got ' + version)
812
+ if (version > 1) {
813
+ throw INVALID_OPLOG_VERSION('Invalid header version. Expected <= 1, got ' + version)
814
+ }
815
+
816
+ if (version === 0) {
817
+ const old = {
818
+ types: types.decode(state),
819
+ userData: keyValueArray.decode(state),
820
+ tree: treeHeader.decode(state),
821
+ signer: keyPair.decode(state),
822
+ hints: hints.decode(state)
823
+ }
824
+
825
+ return {
826
+ key: old.signer.publicKey,
827
+ manifest: {
828
+ namespace: b4a.alloc(32),
829
+ hash: old.types.tree,
830
+ static: null,
831
+ signer: {
832
+ signature: old.types.signer,
833
+ entropy: b4a.alloc(32),
834
+ publicKey: old.signer.publicKey
835
+ },
836
+ multipleSigners: null
837
+ },
838
+ keyPair: old.signer.secretKey ? old.signer : null,
839
+ userData: old.userData,
840
+ tree: old.tree,
841
+ hints: old.hints
842
+ }
843
+ }
844
+
845
+ const flags = c.uint.decode(state)
846
+
847
+ if (flags & 1) {
848
+ return {
849
+ external: externalHeader.decode(state),
850
+ key: null,
851
+ manifest: null,
852
+ keyPair: null,
853
+ userData: null,
854
+ tree: null,
855
+ hints: null
856
+ }
645
857
  }
646
858
 
647
859
  return {
648
- types: types.decode(state),
860
+ external: null,
861
+ key: c.fixed32.decode(state),
862
+ manifest: (flags & 2) !== 0 ? manifest.decode(state) : null,
863
+ keyPair: (flags & 4) !== 0 ? keyPair.decode(state) : null,
649
864
  userData: keyValueArray.decode(state),
650
865
  tree: treeHeader.decode(state),
651
- signer: keyPair.decode(state),
652
- hints: hints.decode(state),
653
- contiguousLength: state.end > state.start ? c.uint.decode(state) : 0
866
+ hints: hints.decode(state)
654
867
  }
655
868
  }
656
869
  }
package/lib/replicator.js CHANGED
@@ -313,6 +313,7 @@ class Peer {
313
313
  this.remoteUploading = true
314
314
  this.remoteDownloading = true
315
315
  this.remoteSynced = false
316
+ this.remoteManifest = false
316
317
 
317
318
  this.segmentsWanted = new Set()
318
319
  this.broadcastedNonSparse = false
@@ -385,7 +386,7 @@ class Peer {
385
386
  })
386
387
  }
387
388
 
388
- onopen ({ capability }) {
389
+ onopen ({ manifest, capability }) {
389
390
  const expected = caps.replicate(this.stream.isInitiator === false, this.replicator.key, this.stream.handshakeHash)
390
391
 
391
392
  if (b4a.equals(capability, expected) !== true) { // TODO: change this to a rejection instead, less leakage
@@ -394,6 +395,7 @@ class Peer {
394
395
 
395
396
  if (this.remoteOpened === true) return
396
397
  this.remoteOpened = true
398
+ this.remoteManifest = manifest
397
399
 
398
400
  this.protomux.cork()
399
401
 
@@ -519,6 +521,10 @@ class Peer {
519
521
  proof.block.value = await this.core.blocks.get(index)
520
522
  }
521
523
 
524
+ if (msg.manifest && !this.core.compat) {
525
+ proof.manifest = this.core.header.manifest
526
+ }
527
+
522
528
  return proof
523
529
  }
524
530
 
@@ -575,7 +581,8 @@ class Peer {
575
581
  block: proof.block,
576
582
  hash: proof.hash,
577
583
  seek: proof.seek,
578
- upgrade: proof.upgrade
584
+ upgrade: proof.upgrade,
585
+ manifest: proof.manifest
579
586
  })
580
587
  return
581
588
  }
@@ -752,12 +759,14 @@ class Peer {
752
759
  upgrade: needsUpgrade === false
753
760
  ? null
754
761
  : { start: this.core.tree.length, length: this.remoteLength - this.core.tree.length },
762
+ // remote manifest check can be removed eventually...
763
+ manifest: needsUpgrade && !this.core.header.manifest && this.remoteManifest && !this.core.compat,
755
764
  priority
756
765
  }
757
766
  }
758
767
 
759
768
  _requestUpgrade (u) {
760
- const req = this._makeRequest(true)
769
+ const req = this._makeRequest(true, 0)
761
770
  if (req === null) return false
762
771
 
763
772
  this._send(req)
@@ -771,7 +780,7 @@ class Peer {
771
780
  if (fork !== this.remoteFork) return false
772
781
 
773
782
  if (s.seeker.start >= length) {
774
- const req = this._makeRequest(true)
783
+ const req = this._makeRequest(true, 0)
775
784
 
776
785
  // We need an upgrade for the seek, if non can be provided, skip
777
786
  if (req === null) return false
@@ -913,9 +922,10 @@ class Peer {
913
922
  }
914
923
 
915
924
  _requestForkProof (f) {
916
- const req = this._makeRequest(false)
925
+ const req = this._makeRequest(false, 0)
917
926
 
918
927
  req.upgrade = { start: 0, length: this.remoteLength }
928
+ req.manifest = !this.core.header.manifest
919
929
 
920
930
  f.inflight.push(req)
921
931
  this._send(req)
@@ -936,7 +946,7 @@ class Peer {
936
946
 
937
947
  if (this.remoteBitfield.get(index) === false) continue
938
948
 
939
- const req = this._makeRequest(false)
949
+ const req = this._makeRequest(false, 0)
940
950
 
941
951
  req.hash = { index: 2 * index, nodes: f.batch.want.nodes }
942
952
 
@@ -1770,6 +1780,7 @@ module.exports = class Replicator {
1770
1780
  const stream = protomux.stream
1771
1781
 
1772
1782
  peer.channel.open({
1783
+ manifest: true,
1773
1784
  capability: caps.replicate(stream.isInitiator, this.key, stream.handshakeHash)
1774
1785
  })
1775
1786
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.20.2",
3
+ "version": "10.21.0",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {