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 +23 -29
- package/lib/big-header.js +55 -0
- package/lib/caps.js +15 -1
- package/lib/core.js +122 -50
- package/lib/merkle-tree.js +5 -2
- package/lib/messages.js +235 -22
- package/lib/oplog.js +2 -1
- package/lib/replicator.js +19 -7
- package/package.json +2 -2
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 && !!
|
|
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 =
|
|
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.
|
|
295
|
+
this.setKeyPair(keyPair)
|
|
301
296
|
}
|
|
302
297
|
|
|
303
298
|
if (isFirst) {
|
|
304
|
-
await this._openCapabilities(
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
379
|
-
this.keyPair = this.core.header.
|
|
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',
|
|
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,
|
|
50
|
-
|
|
51
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
117
|
+
reorgs: [],
|
|
118
|
+
contiguousLength: 0
|
|
119
|
+
}
|
|
106
120
|
}
|
|
107
121
|
|
|
108
|
-
await oplog
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 () {}
|
package/lib/merkle-tree.js
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
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)
|
|
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.
|
|
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
|
-
|
|
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 +=
|
|
624
|
-
|
|
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
|
-
|
|
633
|
-
|
|
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
|
|
644
|
-
throw INVALID_OPLOG_VERSION('Invalid header version. Expected
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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.
|
|
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",
|