hypercore 10.20.1 → 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._signed(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,34 @@ module.exports = class Core {
423
445
  }
424
446
  }
425
447
 
426
- _signed (batch, hash, auth = this.defaultAuth) {
448
+ _verifyBatchUpgrade (batch, manifest) {
449
+ const hash = batch.hash()
427
450
  const signable = this._legacy ? batch.signableLegacy(hash) : batch.signable(hash)
428
- return auth.verify(signable, batch.signature, batch)
429
- }
430
451
 
431
- async _verifyExclusive ({ batch, bitfield, value, from }) {
432
- // TODO: move this to tree.js
433
- const hash = batch.hash()
434
- if (!batch.signature || !this._signed(batch, hash)) {
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 })
461
+
462
+ if (!batch.signature || !auth.verify(signable, batch.signature, batch)) {
435
463
  throw INVALID_SIGNATURE('Proof contains an invalid signature')
436
464
  }
437
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
+ }
471
+ }
472
+
473
+ async _verifyExclusive ({ batch, bitfield, value, manifest, from }) {
474
+ this._verifyBatchUpgrade(batch, manifest)
475
+
438
476
  await this._mutex.lock()
439
477
 
440
478
  try {
@@ -447,7 +485,7 @@ module.exports = class Core {
447
485
  bitfield
448
486
  }
449
487
 
450
- if (this.preupdate !== null) await this.preupdate(batch, this.header.signer.publicKey)
488
+ if (this.preupdate !== null) await this.preupdate(batch, this.header.key)
451
489
  if (bitfield) await this._writeBlock(batch, bitfield.start, value)
452
490
 
453
491
  await this.oplog.append([entry], false)
@@ -463,7 +501,7 @@ module.exports = class Core {
463
501
 
464
502
  this.header.tree.fork = batch.fork
465
503
  this.header.tree.length = batch.length
466
- this.header.tree.rootHash = batch.rootHash
504
+ this.header.tree.rootHash = batch.hash()
467
505
  this.header.tree.signature = batch.signature
468
506
 
469
507
  this.onupdate(status, bitfield, value, from)
@@ -541,9 +579,7 @@ module.exports = class Core {
541
579
 
542
580
  const batch = this.tree.verifyFullyRemote(proof)
543
581
 
544
- if (!batch.signature || !this._signed(batch, batch.hash())) {
545
- throw INVALID_SIGNATURE('Proof contains an invalid signature with no input from us')
546
- }
582
+ this._verifyBatchUpgrade(batch, proof.manifest)
547
583
 
548
584
  const remoteTreeHash = this.crypto.tree(proof.upgrade.nodes)
549
585
  const localTreeHash = this.crypto.tree(await this.tree.getRoots(proof.upgrade.length))
@@ -554,11 +590,18 @@ module.exports = class Core {
554
590
  return true
555
591
  }
556
592
 
593
+ async verifyReorg (proof) {
594
+ const batch = await this.tree.reorg(proof)
595
+
596
+ this._verifyBatchUpgrade(batch, proof.manifest)
597
+
598
+ return batch
599
+ }
600
+
557
601
  async verify (proof, from) {
558
602
  // We cannot apply "other forks" atm.
559
603
  // We should probably still try and they are likely super similar for non upgrades
560
604
  // but this is easy atm (and the above layer will just retry)
561
-
562
605
  if (proof.fork !== this.tree.fork) return false
563
606
 
564
607
  const batch = await this.tree.verify(proof)
@@ -569,6 +612,7 @@ module.exports = class Core {
569
612
  batch,
570
613
  bitfield: value && { drop: false, start: proof.block.index, length: 1 },
571
614
  value,
615
+ manifest: proof.manifest,
572
616
  from
573
617
  }
574
618
 
@@ -646,7 +690,8 @@ module.exports = class Core {
646
690
  this.oplog.close(),
647
691
  this.bitfield.close(),
648
692
  this.tree.close(),
649
- this.blocks.close()
693
+ this.blocks.close(),
694
+ this.bigHeader.close()
650
695
  ])
651
696
  }
652
697
  }
@@ -654,7 +699,7 @@ module.exports = class Core {
654
699
  function updateContig (header, upd, bitfield) {
655
700
  const end = upd.start + upd.length
656
701
 
657
- let c = header.contiguousLength
702
+ let c = header.hints.contiguousLength
658
703
 
659
704
  if (upd.drop) {
660
705
  // If we dropped a block in the current contig range, "downgrade" it
@@ -668,16 +713,16 @@ function updateContig (header, upd, bitfield) {
668
713
  }
669
714
  }
670
715
 
671
- if (c === header.contiguousLength) {
716
+ if (c === header.hints.contiguousLength) {
672
717
  return 0b0000
673
718
  }
674
719
 
675
- if (c > header.contiguousLength) {
676
- header.contiguousLength = c
720
+ if (c > header.hints.contiguousLength) {
721
+ header.hints.contiguousLength = c
677
722
  return 0b0100
678
723
  }
679
724
 
680
- header.contiguousLength = c
725
+ header.hints.contiguousLength = c
681
726
  return 0b1000
682
727
  }
683
728
 
@@ -725,4 +770,31 @@ function closeAll (...storages) {
725
770
  })
726
771
  }
727
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
+
728
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/oplog.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const cenc = require('compact-encoding')
2
2
  const b4a = require('b4a')
3
3
  const { crc32 } = require('crc-universal')
4
- const { OPLOG_CORRUPT } = require('hypercore-errors')
4
+ const { OPLOG_CORRUPT, OPLOG_HEADER_OVERFLOW } = require('hypercore-errors')
5
5
 
6
6
  module.exports = class Oplog {
7
7
  constructor (storage, { pageSize = 4096, headerEncoding = cenc.raw, entryEncoding = cenc.raw, readonly = false } = {}) {
@@ -155,6 +155,7 @@ module.exports = class Oplog {
155
155
  const bit = (this._headers[i] + 1) & 1
156
156
 
157
157
  this.headerEncoding.preencode(state, header)
158
+ if (state.end > this._pageSize) throw OPLOG_HEADER_OVERFLOW()
158
159
  state.buffer = b4a.allocUnsafe(state.end)
159
160
  this.headerEncoding.encode(state, header)
160
161
  this._addHeader(state, state.end - 8, bit, 0)
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
 
@@ -1526,6 +1536,7 @@ module.exports = class Replicator {
1526
1536
  }
1527
1537
 
1528
1538
  async _onreorgdata (peer, req, data) {
1539
+ const newBatch = data.upgrade && await this.core.verifyReorg(data)
1529
1540
  const f = this._addReorg(data.fork, peer)
1530
1541
 
1531
1542
  if (f === null) {
@@ -1538,7 +1549,7 @@ module.exports = class Replicator {
1538
1549
  if (f.batch) {
1539
1550
  await f.batch.update(data)
1540
1551
  } else if (data.upgrade) {
1541
- f.batch = await this.core.tree.reorg(data)
1552
+ f.batch = newBatch
1542
1553
 
1543
1554
  // Remove "older" reorgs in progress as we just verified this one.
1544
1555
  this._clearOldReorgs(f.fork)
@@ -1769,6 +1780,7 @@ module.exports = class Replicator {
1769
1780
  const stream = protomux.stream
1770
1781
 
1771
1782
  peer.channel.open({
1783
+ manifest: true,
1772
1784
  capability: caps.replicate(stream.isInitiator, this.key, stream.handshakeHash)
1773
1785
  })
1774
1786
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.20.1",
3
+ "version": "10.21.0",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -44,7 +44,7 @@
44
44
  "fast-fifo": "^1.3.0",
45
45
  "flat-tree": "^1.9.0",
46
46
  "hypercore-crypto": "^3.2.1",
47
- "hypercore-errors": "^1.0.0",
47
+ "hypercore-errors": "^1.1.0",
48
48
  "is-options": "^1.0.1",
49
49
  "protomux": "^3.5.0",
50
50
  "quickbit-universal": "^2.1.1",