hypercore 10.0.0-alpha.23 → 10.0.0-alpha.26

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/README.md CHANGED
@@ -101,6 +101,10 @@ Truncate the core to a smaller length.
101
101
  Per default this will update the fork id of the core to `+ 1`, but you can set the fork id you prefer with the option.
102
102
  Note that the fork id should be monotonely incrementing.
103
103
 
104
+ #### `const hash = await core.treeHash([length])`
105
+
106
+ Get the Merkle Tree hash of the core at a given length, defaulting to the current length of the core.
107
+
104
108
  #### `const stream = core.createReadStream([options])`
105
109
 
106
110
  Make a read stream. Options include:
@@ -212,6 +216,12 @@ Buffer containing the public key identifying this core.
212
216
 
213
217
  Populated after `ready` has been emitted. Will be `null` before the event.
214
218
 
219
+ #### `core.keyPair`
220
+
221
+ Object containing buffers of the core's public and secret key
222
+
223
+ Populated after `ready` has been emitted. Will be `null` before the event.
224
+
215
225
  #### `core.discoveryKey`
216
226
 
217
227
  Buffer containing a key derived from the core's public key.
@@ -274,6 +284,6 @@ socket.pipe(localCore.replicate(true)).pipe(socket)
274
284
 
275
285
  Emitted when the core has been appended to (i.e. has a new length / byteLength), either locally or remotely.
276
286
 
277
- #### `core.on('truncate')`
287
+ #### `core.on('truncate', ancestors, forkId)`
278
288
 
279
289
  Emitted when the core has been truncated, either locally or remotely.
package/index.js CHANGED
@@ -6,12 +6,12 @@ const c = require('compact-encoding')
6
6
  const b4a = require('b4a')
7
7
  const Xache = require('xache')
8
8
  const NoiseSecretStream = require('@hyperswarm/secret-stream')
9
+ const Protomux = require('protomux')
9
10
  const codecs = require('codecs')
10
11
 
11
12
  const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
12
13
 
13
14
  const Replicator = require('./lib/replicator')
14
- const Extensions = require('./lib/extensions')
15
15
  const Core = require('./lib/core')
16
16
  const BlockEncryption = require('./lib/block-encryption')
17
17
  const { ReadStream, WriteStream } = require('./lib/streams')
@@ -51,14 +51,15 @@ module.exports = class Hypercore extends EventEmitter {
51
51
  this.core = null
52
52
  this.replicator = null
53
53
  this.encryption = null
54
- this.extensions = opts.extensions || new Extensions()
54
+ this.extensions = opts.extensions || new Map()
55
55
  this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
56
56
 
57
57
  this.valueEncoding = null
58
58
  this.encodeBatch = null
59
+ this.activeRequests = []
59
60
 
60
61
  this.key = key || null
61
- this.discoveryKey = null
62
+ this.keyPair = null
62
63
  this.readable = true
63
64
  this.writable = false
64
65
  this.opened = false
@@ -72,6 +73,7 @@ module.exports = class Hypercore extends EventEmitter {
72
73
  this.opening.catch(noop)
73
74
 
74
75
  this._preappend = preappend.bind(this)
76
+ this._snapshot = opts.snapshot || null
75
77
  }
76
78
 
77
79
  [inspect] (depth, opts) {
@@ -92,10 +94,17 @@ module.exports = class Hypercore extends EventEmitter {
92
94
  indent + ')'
93
95
  }
94
96
 
97
+ static protomux (stream, opts) {
98
+ return stream.noiseStream.userData.open(opts)
99
+ }
100
+
95
101
  static createProtocolStream (isInitiator, opts = {}) {
96
- let outerStream = isStream(isInitiator)
97
- ? isInitiator
98
- : opts.stream
102
+ let outerStream = Protomux.isProtomux(isInitiator)
103
+ ? isInitiator.stream
104
+ : isStream(isInitiator)
105
+ ? isInitiator
106
+ : opts.stream
107
+
99
108
  let noiseStream = null
100
109
 
101
110
  if (outerStream) {
@@ -107,10 +116,16 @@ module.exports = class Hypercore extends EventEmitter {
107
116
  if (!noiseStream) throw new Error('Invalid stream')
108
117
 
109
118
  if (!noiseStream.userData) {
110
- const protocol = Replicator.createProtocol(noiseStream, opts)
111
- if (opts.keepAlive !== false) protocol.setKeepAlive(true)
119
+ const protocol = new Protomux(noiseStream)
120
+
121
+ if (opts.ondiscoverykey) {
122
+ protocol.pair({ protocol: 'hypercore/alpha' }, opts.ondiscoverykey)
123
+ }
124
+ if (opts.keepAlive !== false) {
125
+ noiseStream.setKeepAlive(5000)
126
+ noiseStream.setTimeout(7000)
127
+ }
112
128
  noiseStream.userData = protocol
113
- noiseStream.on('error', noop) // All noise errors already propagate through outerStream
114
129
  }
115
130
 
116
131
  return outerStream
@@ -128,6 +143,10 @@ module.exports = class Hypercore extends EventEmitter {
128
143
  }
129
144
  }
130
145
 
146
+ snapshot () {
147
+ return this.session({ snapshot: { length: this.length, byteLength: this.byteLength, fork: this.fork } })
148
+ }
149
+
131
150
  session (opts = {}) {
132
151
  if (this.closing) {
133
152
  // This makes the closing logic alot easier. If this turns out to be a problem
@@ -153,7 +172,6 @@ module.exports = class Hypercore extends EventEmitter {
153
172
  if (!this.sign) this.sign = o.sign
154
173
  this.crypto = o.crypto
155
174
  this.key = o.key
156
- this.discoveryKey = o.discoveryKey
157
175
  this.core = o.core
158
176
  this.replicator = o.replicator
159
177
  this.encryption = o.encryption
@@ -231,10 +249,12 @@ module.exports = class Hypercore extends EventEmitter {
231
249
  this.storage = Hypercore.defaultStorage(opts.storage || storage)
232
250
 
233
251
  this.core = await Core.open(this.storage, {
252
+ force: opts.force,
234
253
  createIfMissing: opts.createIfMissing,
235
254
  overwrite: opts.overwrite,
236
255
  keyPair,
237
256
  crypto: this.crypto,
257
+ legacy: opts.legacy,
238
258
  onupdate: this._oncoreupdate.bind(this)
239
259
  })
240
260
 
@@ -244,18 +264,19 @@ module.exports = class Hypercore extends EventEmitter {
244
264
  }
245
265
  }
246
266
 
247
- this.replicator = new Replicator(this.core, {
248
- onupdate: this._onpeerupdate.bind(this)
249
- })
250
-
251
- this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
252
267
  this.key = this.core.header.signer.publicKey
268
+ this.keyPair = this.core.header.signer
269
+
270
+ this.replicator = new Replicator(this.core, this.key, {
271
+ eagerUpdate: true,
272
+ allowFork: opts.allowFork !== false,
273
+ onpeerupdate: this._onpeerupdate.bind(this),
274
+ onupload: this._onupload.bind(this)
275
+ })
253
276
 
254
277
  if (!this.encryption && opts.encryptionKey) {
255
278
  this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
256
279
  }
257
-
258
- this.extensions.attach(this.replicator)
259
280
  }
260
281
 
261
282
  close () {
@@ -274,6 +295,17 @@ module.exports = class Hypercore extends EventEmitter {
274
295
  this.readable = false
275
296
  this.writable = false
276
297
  this.closed = true
298
+ this.opened = false
299
+
300
+ const gc = []
301
+ for (const ext of this.extensions.values()) {
302
+ if (ext.session === this) gc.push(ext)
303
+ }
304
+ for (const ext of gc) ext.destroy()
305
+
306
+ if (this.replicator !== null) {
307
+ this.replicator.clearRequests(this.activeRequests)
308
+ }
277
309
 
278
310
  if (this.sessions.length) {
279
311
  // if this is the last session and we are auto closing, trigger that first to enforce error handling
@@ -294,24 +326,34 @@ module.exports = class Hypercore extends EventEmitter {
294
326
  const protocol = noiseStream.userData
295
327
 
296
328
  if (this.opened) {
297
- this.replicator.joinProtocol(protocol, this.key, this.discoveryKey)
329
+ this.replicator.attachTo(protocol)
298
330
  } else {
299
- this.opening.then(() => this.replicator.joinProtocol(protocol, this.key, this.discoveryKey), protocol.destroy.bind(protocol))
331
+ this.opening.then(() => this.replicator.attachTo(protocol), protocol.destroy.bind(protocol))
300
332
  }
301
333
 
302
334
  return protocolStream
303
335
  }
304
336
 
337
+ get discoveryKey () {
338
+ return this.replicator === null ? null : this.replicator.discoveryKey
339
+ }
340
+
305
341
  get length () {
306
- return this.core === null ? 0 : this.core.tree.length
342
+ return this._snapshot
343
+ ? this._snapshot.length
344
+ : (this.core === null ? 0 : this.core.tree.length)
307
345
  }
308
346
 
309
347
  get byteLength () {
310
- return this.core === null ? 0 : this.core.tree.byteLength - (this.core.tree.length * this.padding)
348
+ return this._snapshot
349
+ ? this._snapshot.byteLength
350
+ : (this.core === null ? 0 : this.core.tree.byteLength - (this.core.tree.length * this.padding))
311
351
  }
312
352
 
313
353
  get fork () {
314
- return this.core === null ? 0 : this.core.tree.fork
354
+ return this._snapshot
355
+ ? this._snapshot.fork
356
+ : (this.core === null ? 0 : this.core.tree.fork)
315
357
  }
316
358
 
317
359
  get peers () {
@@ -330,25 +372,31 @@ module.exports = class Hypercore extends EventEmitter {
330
372
  return this.opening
331
373
  }
332
374
 
375
+ _onupload (index, value, from) {
376
+ const byteLength = value.byteLength - this.padding
377
+
378
+ for (let i = 0; i < this.sessions.length; i++) {
379
+ this.sessions[i].emit('upload', index, byteLength, from)
380
+ }
381
+ }
382
+
333
383
  _oncoreupdate (status, bitfield, value, from) {
334
384
  if (status !== 0) {
335
385
  for (let i = 0; i < this.sessions.length; i++) {
336
386
  if ((status & 0b10) !== 0) {
337
387
  if (this.cache) this.cache.clear()
338
- this.sessions[i].emit('truncate', this.core.tree.fork)
388
+ this.sessions[i].emit('truncate', bitfield.start, this.core.tree.fork)
339
389
  }
340
390
  if ((status & 0b01) !== 0) {
341
391
  this.sessions[i].emit('append')
342
392
  }
343
393
  }
344
394
 
345
- this.replicator.broadcastInfo()
395
+ this.replicator.signalUpgrade()
346
396
  }
347
397
 
348
- if (bitfield && !bitfield.drop) { // TODO: support drop!
349
- for (let i = 0; i < bitfield.length; i++) {
350
- this.replicator.broadcastBlock(bitfield.start + i)
351
- }
398
+ if (bitfield) {
399
+ this.replicator.broadcastRange(bitfield.start, bitfield.length, bitfield.drop)
352
400
  }
353
401
 
354
402
  if (value) {
@@ -361,9 +409,14 @@ module.exports = class Hypercore extends EventEmitter {
361
409
  }
362
410
 
363
411
  _onpeerupdate (added, peer) {
364
- if (added) this.extensions.update(peer)
365
412
  const name = added ? 'peer-add' : 'peer-remove'
366
413
 
414
+ if (added) {
415
+ for (const ext of this.extensions.values()) {
416
+ peer.extensions.set(ext.name, ext)
417
+ }
418
+ }
419
+
367
420
  for (let i = 0; i < this.sessions.length; i++) {
368
421
  this.sessions[i].emit(name, peer)
369
422
  }
@@ -382,19 +435,32 @@ module.exports = class Hypercore extends EventEmitter {
382
435
  return null
383
436
  }
384
437
 
385
- async update () {
438
+ async update (opts) {
386
439
  if (this.opened === false) await this.opening
440
+
387
441
  // TODO: add an option where a writer can bootstrap it's state from the network also
388
- if (this.writable) return false
389
- return this.replicator.requestUpgrade()
442
+ if (this.writable || this.closing !== null) return false
443
+
444
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
445
+ const req = this.replicator.addUpgrade(activeRequests)
446
+
447
+ return req.promise
390
448
  }
391
449
 
392
- async seek (bytes) {
450
+ async seek (bytes, opts) {
393
451
  if (this.opened === false) await this.opening
394
452
 
395
453
  const s = this.core.tree.seek(bytes, this.padding)
396
454
 
397
- return (await s.update()) || this.replicator.requestSeek(s)
455
+ const offset = await s.update()
456
+ if (offset) return offset
457
+
458
+ if (this.closing !== null) throw new Error('Session is closed')
459
+
460
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
461
+ const req = this.replicator.addSeek(activeRequests, s)
462
+
463
+ return req.promise
398
464
  }
399
465
 
400
466
  async has (index) {
@@ -405,6 +471,8 @@ module.exports = class Hypercore extends EventEmitter {
405
471
 
406
472
  async get (index, opts) {
407
473
  if (this.opened === false) await this.opening
474
+ if (this.closing !== null) throw new Error('Session is closed')
475
+
408
476
  const c = this.cache && this.cache.get(index)
409
477
  if (c) return c
410
478
  const fork = this.core.tree.fork
@@ -423,7 +491,11 @@ module.exports = class Hypercore extends EventEmitter {
423
491
  } else {
424
492
  if (opts && opts.wait === false) return null
425
493
  if (opts && opts.onwait) opts.onwait(index)
426
- block = await this.replicator.requestBlock(index)
494
+
495
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
496
+ const req = this.replicator.addBlock(activeRequests, index)
497
+
498
+ block = await req.promise
427
499
  }
428
500
 
429
501
  if (this.encryption) this.encryption.decrypt(index, block)
@@ -439,37 +511,27 @@ module.exports = class Hypercore extends EventEmitter {
439
511
  }
440
512
 
441
513
  download (range) {
442
- const linear = !!(range && range.linear)
443
-
444
- let start
445
- let end
446
- let filter
447
-
448
- if (range && range.blocks) {
449
- const blocks = range.blocks instanceof Set
450
- ? range.blocks
451
- : new Set(range.blocks)
452
-
453
- start = range.start || (blocks.size ? min(range.blocks) : 0)
454
- end = range.end || (blocks.size ? max(range.blocks) + 1 : 0)
455
-
456
- filter = (i) => blocks.has(i)
457
- } else {
458
- start = (range && range.start) || 0
459
- end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
514
+ const reqP = this._download(range)
515
+
516
+ // do not crash in the background...
517
+ reqP.catch(noop)
518
+
519
+ // TODO: turn this into an actual object...
520
+ return {
521
+ async downloaded () {
522
+ const req = await reqP
523
+ return req.promise
524
+ },
525
+ destroy () {
526
+ reqP.then(req => req.context && req.context.detach(req), noop)
527
+ }
460
528
  }
461
-
462
- const r = Replicator.createRange(start, end, filter, linear)
463
-
464
- if (this.opened) this.replicator.addRange(r)
465
- else this.opening.then(() => this.replicator.addRange(r), noop)
466
-
467
- return r
468
529
  }
469
530
 
470
- // TODO: get rid of this / deprecate it?
471
- cancel (request) {
472
- // Do nothing for now
531
+ async _download (range) {
532
+ if (this.opened === false) await this.opening
533
+ const activeRequests = (range && range.activeRequests) || this.activeRequests
534
+ return this.replicator.addRange(activeRequests, range)
473
535
  }
474
536
 
475
537
  // TODO: get rid of this / deprecate it?
@@ -477,6 +539,11 @@ module.exports = class Hypercore extends EventEmitter {
477
539
  range.destroy(null)
478
540
  }
479
541
 
542
+ // TODO: get rid of this / deprecate it?
543
+ cancel (request) {
544
+ // Do nothing for now
545
+ }
546
+
480
547
  async truncate (newLength = 0, fork = -1) {
481
548
  if (this.opened === false) await this.opening
482
549
  if (this.writable === false) throw new Error('Core is not writable')
@@ -507,13 +574,58 @@ module.exports = class Hypercore extends EventEmitter {
507
574
  return await this.core.append(buffers, this.sign, { preappend })
508
575
  }
509
576
 
510
- registerExtension (name, handlers) {
511
- return this.extensions.register(name, handlers)
577
+ async treeHash (length) {
578
+ if (length === undefined) {
579
+ await this.ready()
580
+ length = this.core.length
581
+ }
582
+
583
+ const roots = await this.core.tree.getRoots(length)
584
+ return this.crypto.tree(roots)
512
585
  }
513
586
 
514
- // called by the extensions
515
- onextensionupdate () {
516
- if (this.replicator !== null) this.replicator.broadcastOptions()
587
+ registerExtension (name, handlers = {}) {
588
+ if (this.extensions.has(name)) {
589
+ const ext = this.extensions.get(name)
590
+ ext.handlers = handlers
591
+ ext.encoding = c.from(codecs(handlers.encoding) || c.buffer)
592
+ ext.session = this
593
+ return ext
594
+ }
595
+
596
+ const ext = {
597
+ name,
598
+ handlers,
599
+ encoding: c.from(codecs(handlers.encoding) || c.buffer),
600
+ session: this,
601
+ send (message, peer) {
602
+ const buffer = c.encode(this.encoding, message)
603
+ peer.extension(name, buffer)
604
+ },
605
+ broadcast (message) {
606
+ const buffer = c.encode(this.encoding, message)
607
+ for (const peer of this.session.peers) {
608
+ peer.extension(name, buffer)
609
+ }
610
+ },
611
+ destroy () {
612
+ for (const peer of this.session.peers) {
613
+ peer.extensions.delete(name)
614
+ }
615
+ this.session.extensions.delete(name)
616
+ },
617
+ _onmessage (state, peer) {
618
+ const m = this.encoding.decode(state)
619
+ if (this.handlers.onmessage) this.handlers.onmessage(m, peer)
620
+ }
621
+ }
622
+
623
+ this.extensions.set(name, ext)
624
+ for (const peer of this.peers) {
625
+ peer.extensions.set(name, ext)
626
+ }
627
+
628
+ return ext
517
629
  }
518
630
 
519
631
  _encode (enc, val) {
@@ -563,19 +675,6 @@ function toHex (buf) {
563
675
  return buf && b4a.toString(buf, 'hex')
564
676
  }
565
677
 
566
- function reduce (iter, fn, acc) {
567
- for (const item of iter) acc = fn(acc, item)
568
- return acc
569
- }
570
-
571
- function min (arr) {
572
- return reduce(arr, (a, b) => Math.min(a, b), Infinity)
573
- }
574
-
575
- function max (arr) {
576
- return reduce(arr, (a, b) => Math.max(a, b), -Infinity)
577
- }
578
-
579
678
  function preappend (blocks) {
580
679
  const offset = this.core.tree.length
581
680
  const fork = this.core.tree.fork
package/lib/bitfield.js CHANGED
@@ -41,8 +41,9 @@ module.exports = class Bitfield {
41
41
  this.pages = new BigSparseArray()
42
42
  this.unflushed = []
43
43
  this.storage = storage
44
+ this.resumed = !!(buf && buf.byteLength >= 4)
44
45
 
45
- const all = (buf && buf.byteLength >= 4)
46
+ const all = this.resumed
46
47
  ? new Uint32Array(buf.buffer, buf.byteOffset, Math.floor(buf.byteLength / 4))
47
48
  : new Uint32Array(1024)
48
49
 
@@ -93,8 +94,10 @@ module.exports = class Bitfield {
93
94
  clear () {
94
95
  return new Promise((resolve, reject) => {
95
96
  this.storage.del(0, Infinity, (err) => {
96
- if (err) reject(err)
97
- else resolve()
97
+ if (err) return reject(err)
98
+ this.pages = new BigSparseArray()
99
+ this.unflushed = []
100
+ resolve()
98
101
  })
99
102
  })
100
103
  }
package/lib/caps.js ADDED
@@ -0,0 +1,34 @@
1
+ const crypto = require('hypercore-crypto')
2
+ const sodium = require('sodium-universal')
3
+ const b4a = require('b4a')
4
+ const c = require('compact-encoding')
5
+
6
+ // TODO: rename this to "crypto" and move everything hashing related etc in here
7
+ // Also lets move the tree stuff from hypercore-crypto here, and loose the types
8
+ // from the hashes there - they are not needed since we lock the indexes in the tree
9
+ // hash and just makes alignment etc harder in other languages
10
+
11
+ const [TREE, REPLICATE_INITIATOR, REPLICATE_RESPONDER] = crypto.namespace('hypercore', 3)
12
+
13
+ exports.replicate = function (isInitiator, key, handshakeHash) {
14
+ const out = b4a.allocUnsafe(32)
15
+ sodium.crypto_generichash_batch(out, [isInitiator ? REPLICATE_INITIATOR : REPLICATE_RESPONDER, key], handshakeHash)
16
+ return out
17
+ }
18
+
19
+ exports.treeSignable = function (hash, length, fork) {
20
+ const state = { start: 0, end: 80, buffer: b4a.allocUnsafe(80) }
21
+ c.raw.encode(state, TREE)
22
+ c.raw.encode(state, hash)
23
+ c.uint64.encode(state, length)
24
+ c.uint64.encode(state, fork)
25
+ return state.buffer
26
+ }
27
+
28
+ exports.treeSignableLegacy = function (hash, length, fork) {
29
+ const state = { start: 0, end: 48, buffer: b4a.allocUnsafe(48) }
30
+ c.raw.encode(state, hash)
31
+ c.uint64.encode(state, length)
32
+ c.uint64.encode(state, fork)
33
+ return state.buffer
34
+ }
package/lib/core.js CHANGED
@@ -5,10 +5,10 @@ const Mutex = require('./mutex')
5
5
  const MerkleTree = require('./merkle-tree')
6
6
  const BlockStore = require('./block-store')
7
7
  const Bitfield = require('./bitfield')
8
- const { oplogHeader, oplogEntry } = require('./messages')
8
+ const m = require('./messages')
9
9
 
10
10
  module.exports = class Core {
11
- constructor (header, crypto, oplog, tree, blocks, bitfield, sign, onupdate) {
11
+ constructor (header, crypto, oplog, tree, blocks, bitfield, sign, legacy, onupdate) {
12
12
  this.onupdate = onupdate
13
13
  this.header = header
14
14
  this.crypto = crypto
@@ -24,6 +24,7 @@ module.exports = class Core {
24
24
  this._verifies = null
25
25
  this._verifiesFlushed = null
26
26
  this._mutex = new Mutex()
27
+ this._legacy = legacy
27
28
  }
28
29
 
29
30
  static async open (storage, opts = {}) {
@@ -57,18 +58,24 @@ module.exports = class Core {
57
58
  }
58
59
 
59
60
  static async resume (oplogFile, treeFile, bitfieldFile, dataFile, opts) {
60
- const overwrite = opts.overwrite === true
61
+ let overwrite = opts.overwrite === true
62
+
63
+ const force = opts.force === true
61
64
  const createIfMissing = opts.createIfMissing !== false
62
65
  const crypto = opts.crypto || hypercoreCrypto
63
66
 
64
67
  const oplog = new Oplog(oplogFile, {
65
- headerEncoding: oplogHeader,
66
- entryEncoding: oplogEntry
68
+ headerEncoding: m.oplog.header,
69
+ entryEncoding: m.oplog.entry
67
70
  })
68
71
 
69
72
  let { header, entries } = await oplog.open()
70
73
 
71
- if (!header || overwrite === true) {
74
+ if (force && opts.keyPair && header && header.signer && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
75
+ overwrite = true
76
+ }
77
+
78
+ if (!header || overwrite) {
72
79
  if (!createIfMissing) {
73
80
  throw new Error('No hypercore is stored here')
74
81
  }
@@ -103,6 +110,10 @@ module.exports = class Core {
103
110
  await tree.clear()
104
111
  await blocks.clear()
105
112
  await bitfield.clear()
113
+ entries = []
114
+ } else if (bitfield.resumed && header.tree.length === 0) {
115
+ // If this was an old bitfield, reset it since it loads based on disk size atm (TODO: change that)
116
+ await bitfield.clear()
106
117
  }
107
118
 
108
119
  const sign = opts.sign || (header.signer.secretKey ? this.createSigner(crypto, header.signer) : null)
@@ -119,7 +130,7 @@ module.exports = class Core {
119
130
  }
120
131
 
121
132
  if (e.bitfield) {
122
- bitfield.setRange(e.bitfield.start, e.bitfield.length)
133
+ bitfield.setRange(e.bitfield.start, e.bitfield.length, !e.bitfield.drop)
123
134
  }
124
135
 
125
136
  if (e.treeUpgrade) {
@@ -136,7 +147,7 @@ module.exports = class Core {
136
147
  }
137
148
  }
138
149
 
139
- return new this(header, crypto, oplog, tree, blocks, bitfield, sign, opts.onupdate || noop)
150
+ return new this(header, crypto, oplog, tree, blocks, bitfield, sign, !!opts.legacy, opts.onupdate || noop)
140
151
  }
141
152
 
142
153
  _shouldFlush () {
@@ -227,7 +238,7 @@ module.exports = class Core {
227
238
  for (const val of values) batch.append(val)
228
239
 
229
240
  const hash = batch.hash()
230
- batch.signature = await sign(batch.signable(hash))
241
+ batch.signature = await sign(this._legacy ? batch.signableLegacy(hash) : batch.signable(hash))
231
242
 
232
243
  const entry = {
233
244
  userData: null,
@@ -259,10 +270,15 @@ module.exports = class Core {
259
270
  }
260
271
  }
261
272
 
273
+ _signed (batch, hash) {
274
+ const signable = this._legacy ? batch.signableLegacy(hash) : batch.signable(hash)
275
+ return this.crypto.verify(signable, batch.signature, this.header.signer.publicKey)
276
+ }
277
+
262
278
  async _verifyExclusive ({ batch, bitfield, value, from }) {
263
279
  // TODO: move this to tree.js
264
280
  const hash = batch.hash()
265
- if (!batch.signature || !this.crypto.verify(batch.signable(hash), batch.signature, this.header.signer.publicKey)) {
281
+ if (!batch.signature || !this._signed(batch, hash)) {
266
282
  throw new Error('Remote signature does not match')
267
283
  }
268
284