hypercore 11.16.2 → 11.17.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/README.md +31 -29
- package/index.js +212 -127
- package/lib/audit.js +17 -6
- package/lib/bit-interlude.js +17 -7
- package/lib/bitfield.js +72 -52
- package/lib/caps.js +5 -1
- package/lib/copy-prologue.js +14 -10
- package/lib/core.js +109 -56
- package/lib/default-encryption.js +14 -28
- package/lib/download.js +10 -10
- package/lib/fully-remote-proof.js +3 -3
- package/lib/hotswap-queue.js +5 -5
- package/lib/info.js +4 -4
- package/lib/merkle-tree.js +143 -104
- package/lib/messages.js +163 -143
- package/lib/multisig.js +19 -12
- package/lib/mutex.js +9 -7
- package/lib/receiver-queue.js +6 -6
- package/lib/remote-bitfield.js +30 -32
- package/lib/replicator.js +383 -265
- package/lib/session-state.js +112 -75
- package/lib/streams.js +16 -16
- package/lib/verifier.js +69 -43
- package/package.json +5 -3
package/lib/core.js
CHANGED
|
@@ -7,7 +7,13 @@ const { MerkleTree, ReorgBatch } = require('./merkle-tree')
|
|
|
7
7
|
const BitInterlude = require('./bit-interlude')
|
|
8
8
|
const Bitfield = require('./bitfield')
|
|
9
9
|
const RemoteBitfield = require('./remote-bitfield')
|
|
10
|
-
const {
|
|
10
|
+
const {
|
|
11
|
+
BAD_ARGUMENT,
|
|
12
|
+
STORAGE_EMPTY,
|
|
13
|
+
STORAGE_CONFLICT,
|
|
14
|
+
INVALID_SIGNATURE,
|
|
15
|
+
INVALID_CHECKSUM
|
|
16
|
+
} = require('hypercore-errors')
|
|
11
17
|
const Verifier = require('./verifier')
|
|
12
18
|
const audit = require('./audit')
|
|
13
19
|
const copyPrologue = require('./copy-prologue')
|
|
@@ -15,7 +21,7 @@ const SessionState = require('./session-state')
|
|
|
15
21
|
const Replicator = require('./replicator')
|
|
16
22
|
|
|
17
23
|
module.exports = class Core {
|
|
18
|
-
constructor
|
|
24
|
+
constructor(db, opts = {}) {
|
|
19
25
|
this.db = db
|
|
20
26
|
this.storage = null
|
|
21
27
|
this.replicator = new Replicator(this, opts)
|
|
@@ -58,33 +64,33 @@ module.exports = class Core {
|
|
|
58
64
|
this.opening.catch(noop)
|
|
59
65
|
}
|
|
60
66
|
|
|
61
|
-
ready
|
|
67
|
+
ready() {
|
|
62
68
|
return this.opening
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
addMonitor
|
|
71
|
+
addMonitor(s) {
|
|
66
72
|
if (s._monitorIndex >= 0) return
|
|
67
73
|
s._monitorIndex = this.monitors.push(s) - 1
|
|
68
74
|
}
|
|
69
75
|
|
|
70
|
-
removeMonitor
|
|
76
|
+
removeMonitor(s) {
|
|
71
77
|
if (s._monitorIndex < 0) return
|
|
72
78
|
const head = this.monitors.pop()
|
|
73
79
|
if (head !== s) this.monitors[(head._monitorIndex = s._monitorIndex)] = head
|
|
74
80
|
s._monitorIndex = -1
|
|
75
81
|
}
|
|
76
82
|
|
|
77
|
-
emitManifest
|
|
83
|
+
emitManifest() {
|
|
78
84
|
for (let i = this.monitors.length - 1; i >= 0; i--) {
|
|
79
85
|
this.monitors[i].emit('manifest')
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
88
|
|
|
83
|
-
createUserDataStream
|
|
89
|
+
createUserDataStream(opts, session = this.state) {
|
|
84
90
|
return session.storage.createUserDataStream(opts)
|
|
85
91
|
}
|
|
86
92
|
|
|
87
|
-
allSessions
|
|
93
|
+
allSessions() {
|
|
88
94
|
const sessions = []
|
|
89
95
|
for (const state of this.sessionStates) {
|
|
90
96
|
if (state.sessions.length) sessions.push(...state.sessions)
|
|
@@ -92,27 +98,35 @@ module.exports = class Core {
|
|
|
92
98
|
return sessions
|
|
93
99
|
}
|
|
94
100
|
|
|
95
|
-
hasSession
|
|
101
|
+
hasSession() {
|
|
96
102
|
return this.activeSessions !== 0
|
|
97
103
|
}
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
compact() {
|
|
106
|
+
const compacting = []
|
|
107
|
+
for (const s of this.sessionStates) {
|
|
108
|
+
compacting.push(s.storage.compact())
|
|
109
|
+
}
|
|
110
|
+
return Promise.all(compacting)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
checkIfIdle() {
|
|
100
114
|
if (!this.opened || this.destroyed === true || this.hasSession() === true) return
|
|
101
115
|
if (this.replicator.idle() === false) return
|
|
102
116
|
if (this.state === null || this.state.mutex.idle() === false) return
|
|
103
117
|
this.onidle()
|
|
104
118
|
}
|
|
105
119
|
|
|
106
|
-
async lockExclusive
|
|
120
|
+
async lockExclusive() {
|
|
107
121
|
if (this.exclusive === null) this.exclusive = new Mutex()
|
|
108
122
|
await this.exclusive.lock()
|
|
109
123
|
}
|
|
110
124
|
|
|
111
|
-
unlockExclusive
|
|
125
|
+
unlockExclusive() {
|
|
112
126
|
if (this.exclusive !== null) this.exclusive.unlock()
|
|
113
127
|
}
|
|
114
128
|
|
|
115
|
-
async _open
|
|
129
|
+
async _open(opts) {
|
|
116
130
|
try {
|
|
117
131
|
await this._tryOpen(opts)
|
|
118
132
|
} catch (err) {
|
|
@@ -123,7 +137,7 @@ module.exports = class Core {
|
|
|
123
137
|
this.opened = true
|
|
124
138
|
}
|
|
125
139
|
|
|
126
|
-
async _tryOpen
|
|
140
|
+
async _tryOpen(opts) {
|
|
127
141
|
if (opts.preopen) await opts.preopen // just a hook to allow exclusive access here...
|
|
128
142
|
|
|
129
143
|
let storage = await this.db.resume(this.discoveryKey)
|
|
@@ -144,7 +158,7 @@ module.exports = class Core {
|
|
|
144
158
|
overwrite = true
|
|
145
159
|
}
|
|
146
160
|
|
|
147
|
-
if (!header &&
|
|
161
|
+
if (!header && opts.discoveryKey && !(opts.key || opts.manifest)) {
|
|
148
162
|
throw STORAGE_EMPTY('No Hypercore is stored here', this.discoveryKey)
|
|
149
163
|
}
|
|
150
164
|
|
|
@@ -161,13 +175,19 @@ module.exports = class Core {
|
|
|
161
175
|
|
|
162
176
|
const keyPair = opts.keyPair || (opts.key ? null : crypto.keyPair())
|
|
163
177
|
|
|
164
|
-
const defaultManifest =
|
|
165
|
-
|
|
178
|
+
const defaultManifest =
|
|
179
|
+
!opts.manifest &&
|
|
180
|
+
(!!opts.compat || !opts.key || !!(keyPair && b4a.equals(opts.key, keyPair.publicKey)))
|
|
181
|
+
const manifest = defaultManifest
|
|
182
|
+
? Verifier.defaultSignerManifest(opts.key || keyPair.publicKey)
|
|
183
|
+
: Verifier.createManifest(opts.manifest)
|
|
166
184
|
|
|
167
185
|
header = {
|
|
168
186
|
key: opts.key || (compat ? manifest.signers[0].publicKey : Verifier.manifestHash(manifest)),
|
|
169
187
|
manifest,
|
|
170
|
-
keyPair: keyPair
|
|
188
|
+
keyPair: keyPair
|
|
189
|
+
? { publicKey: keyPair.publicKey, secretKey: keyPair.secretKey || null }
|
|
190
|
+
: null,
|
|
171
191
|
frozen: false,
|
|
172
192
|
tree: {
|
|
173
193
|
fork: 0,
|
|
@@ -214,7 +234,10 @@ module.exports = class Core {
|
|
|
214
234
|
|
|
215
235
|
if (opts.manifest) {
|
|
216
236
|
// if we provide a manifest and no key, verify that the stored key is the same
|
|
217
|
-
if (
|
|
237
|
+
if (
|
|
238
|
+
!opts.key &&
|
|
239
|
+
!Verifier.isValidManifest(header.key, Verifier.createManifest(opts.manifest))
|
|
240
|
+
) {
|
|
218
241
|
throw STORAGE_CONFLICT('Manifest does not hash to provided key', this.discoveryKey)
|
|
219
242
|
}
|
|
220
243
|
|
|
@@ -240,7 +263,9 @@ module.exports = class Core {
|
|
|
240
263
|
fork: header.tree.fork,
|
|
241
264
|
length: header.tree.length,
|
|
242
265
|
signature: header.tree.signature,
|
|
243
|
-
roots: header.tree.length
|
|
266
|
+
roots: header.tree.length
|
|
267
|
+
? await MerkleTree.getRootsFromStorage(storage, header.tree.length)
|
|
268
|
+
: [],
|
|
244
269
|
prologue
|
|
245
270
|
}
|
|
246
271
|
|
|
@@ -273,7 +298,9 @@ module.exports = class Core {
|
|
|
273
298
|
await tx.flush()
|
|
274
299
|
}
|
|
275
300
|
|
|
276
|
-
const verifier = header.manifest
|
|
301
|
+
const verifier = header.manifest
|
|
302
|
+
? new Verifier(header.key, header.manifest, { crypto, legacy })
|
|
303
|
+
: null
|
|
277
304
|
|
|
278
305
|
this.storage = storage
|
|
279
306
|
this.header = header
|
|
@@ -288,7 +315,7 @@ module.exports = class Core {
|
|
|
288
315
|
if (this.manifest === null) this.manifest = this.header.manifest
|
|
289
316
|
}
|
|
290
317
|
|
|
291
|
-
async audit
|
|
318
|
+
async audit(opts) {
|
|
292
319
|
await this.state.mutex.lock()
|
|
293
320
|
|
|
294
321
|
try {
|
|
@@ -298,12 +325,13 @@ module.exports = class Core {
|
|
|
298
325
|
}
|
|
299
326
|
}
|
|
300
327
|
|
|
301
|
-
async setManifest
|
|
328
|
+
async setManifest(manifest) {
|
|
302
329
|
await this.state.mutex.lock()
|
|
303
330
|
|
|
304
331
|
try {
|
|
305
332
|
if (manifest && this.header.manifest === null) {
|
|
306
|
-
if (!Verifier.isValidManifest(this.header.key, manifest))
|
|
333
|
+
if (!Verifier.isValidManifest(this.header.key, manifest))
|
|
334
|
+
throw INVALID_CHECKSUM('Manifest hash does not match', this.discoveryKey)
|
|
307
335
|
|
|
308
336
|
const tx = this.state.createWriteBatch()
|
|
309
337
|
this._setManifest(tx, Verifier.createManifest(manifest), null)
|
|
@@ -314,8 +342,9 @@ module.exports = class Core {
|
|
|
314
342
|
}
|
|
315
343
|
}
|
|
316
344
|
|
|
317
|
-
_setManifest
|
|
318
|
-
if (!manifest && b4a.equals(keyPair.publicKey, this.header.key))
|
|
345
|
+
_setManifest(tx, manifest, keyPair) {
|
|
346
|
+
if (!manifest && b4a.equals(keyPair.publicKey, this.header.key))
|
|
347
|
+
manifest = Verifier.defaultSignerManifest(this.header.key)
|
|
319
348
|
if (!manifest) return
|
|
320
349
|
|
|
321
350
|
const verifier = new Verifier(this.header.key, manifest, { legacy: this._legacy })
|
|
@@ -339,7 +368,7 @@ module.exports = class Core {
|
|
|
339
368
|
this.emitManifest()
|
|
340
369
|
}
|
|
341
370
|
|
|
342
|
-
async copyPrologue
|
|
371
|
+
async copyPrologue(src) {
|
|
343
372
|
await this.state.mutex.lock()
|
|
344
373
|
|
|
345
374
|
try {
|
|
@@ -358,11 +387,11 @@ module.exports = class Core {
|
|
|
358
387
|
}
|
|
359
388
|
}
|
|
360
389
|
|
|
361
|
-
flushed
|
|
390
|
+
flushed() {
|
|
362
391
|
return this.state.flushed()
|
|
363
392
|
}
|
|
364
393
|
|
|
365
|
-
async _validateCommit
|
|
394
|
+
async _validateCommit(state, treeLength) {
|
|
366
395
|
if (this.state.length > state.length) {
|
|
367
396
|
return false // TODO: partial commit and truncation possible in the future
|
|
368
397
|
}
|
|
@@ -383,17 +412,25 @@ module.exports = class Core {
|
|
|
383
412
|
return true
|
|
384
413
|
}
|
|
385
414
|
|
|
386
|
-
_verifyBatchUpgrade
|
|
415
|
+
_verifyBatchUpgrade(batch, manifest) {
|
|
387
416
|
if (!this.header.manifest) {
|
|
388
417
|
// compat, drop at some point
|
|
389
418
|
if (!manifest) manifest = Verifier.defaultSignerManifest(this.header.key)
|
|
390
419
|
|
|
391
|
-
if (
|
|
420
|
+
if (
|
|
421
|
+
!manifest ||
|
|
422
|
+
!(
|
|
423
|
+
Verifier.isValidManifest(this.header.key, manifest) ||
|
|
424
|
+
Verifier.isCompat(this.header.key, manifest)
|
|
425
|
+
)
|
|
426
|
+
) {
|
|
392
427
|
throw INVALID_SIGNATURE('Proof contains an invalid manifest', this.discoveryKey) // TODO: proper error type
|
|
393
428
|
}
|
|
394
429
|
}
|
|
395
430
|
|
|
396
|
-
const verifier =
|
|
431
|
+
const verifier =
|
|
432
|
+
this.verifier ||
|
|
433
|
+
new Verifier(this.header.key, Verifier.createManifest(manifest), { legacy: this._legacy })
|
|
397
434
|
if (!verifier.verify(batch, batch.signature)) {
|
|
398
435
|
throw INVALID_SIGNATURE('Proof contains an invalid signature', this.discoveryKey)
|
|
399
436
|
}
|
|
@@ -401,14 +438,21 @@ module.exports = class Core {
|
|
|
401
438
|
return manifest
|
|
402
439
|
}
|
|
403
440
|
|
|
404
|
-
async _verifyExclusive
|
|
441
|
+
async _verifyExclusive({ batch, bitfield, value, manifest }) {
|
|
405
442
|
manifest = this._verifyBatchUpgrade(batch, manifest)
|
|
406
443
|
|
|
407
444
|
if (!batch.commitable()) return false
|
|
408
445
|
|
|
409
446
|
if (this.preupdate !== null) await this.preupdate(batch, this.header.key)
|
|
410
447
|
|
|
411
|
-
if (
|
|
448
|
+
if (
|
|
449
|
+
!(await this.state._verifyBlock(
|
|
450
|
+
batch,
|
|
451
|
+
bitfield,
|
|
452
|
+
value,
|
|
453
|
+
this.header.manifest ? null : manifest
|
|
454
|
+
))
|
|
455
|
+
) {
|
|
412
456
|
return false
|
|
413
457
|
}
|
|
414
458
|
|
|
@@ -419,7 +463,7 @@ module.exports = class Core {
|
|
|
419
463
|
return true
|
|
420
464
|
}
|
|
421
465
|
|
|
422
|
-
async _verifyShared
|
|
466
|
+
async _verifyShared() {
|
|
423
467
|
if (!this._verifies.length) return false
|
|
424
468
|
|
|
425
469
|
await this.state.mutex.lock()
|
|
@@ -455,7 +499,8 @@ module.exports = class Core {
|
|
|
455
499
|
|
|
456
500
|
// if we got a manifest AND its strictly a non compat one, lets store it
|
|
457
501
|
if (manifest && this.header.manifest === null) {
|
|
458
|
-
if (!Verifier.isValidManifest(this.header.key, manifest))
|
|
502
|
+
if (!Verifier.isValidManifest(this.header.key, manifest))
|
|
503
|
+
throw INVALID_CHECKSUM('Manifest hash does not match', this.discoveryKey)
|
|
459
504
|
this._setManifest(tx, manifest, null)
|
|
460
505
|
}
|
|
461
506
|
|
|
@@ -485,7 +530,7 @@ module.exports = class Core {
|
|
|
485
530
|
return verifies[0] !== null
|
|
486
531
|
}
|
|
487
532
|
|
|
488
|
-
async checkConflict
|
|
533
|
+
async checkConflict(proof, from) {
|
|
489
534
|
if (this.state.length < proof.upgrade.length || proof.fork !== this.state.fork) {
|
|
490
535
|
// out of date this proof - ignore for now
|
|
491
536
|
return false
|
|
@@ -555,12 +600,20 @@ module.exports = class Core {
|
|
|
555
600
|
|
|
556
601
|
// tmp log so we can see these
|
|
557
602
|
const id = b4a.toString(this.discoveryKey, 'hex')
|
|
558
|
-
console.log(
|
|
603
|
+
console.log(
|
|
604
|
+
'[hypercore] conflict detected in ' +
|
|
605
|
+
id +
|
|
606
|
+
' (writable=' +
|
|
607
|
+
!!this.header.keyPair +
|
|
608
|
+
',quorum=' +
|
|
609
|
+
this.header.manifest.quorum +
|
|
610
|
+
')'
|
|
611
|
+
)
|
|
559
612
|
await this._onconflict(proof)
|
|
560
613
|
return true
|
|
561
614
|
}
|
|
562
615
|
|
|
563
|
-
async verifyReorg
|
|
616
|
+
async verifyReorg(proof) {
|
|
564
617
|
const batch = new ReorgBatch(this.state)
|
|
565
618
|
await MerkleTree.reorg(this.state, proof, batch)
|
|
566
619
|
const manifest = this._verifyBatchUpgrade(batch, proof.manifest)
|
|
@@ -581,7 +634,7 @@ module.exports = class Core {
|
|
|
581
634
|
return batch
|
|
582
635
|
}
|
|
583
636
|
|
|
584
|
-
async verify
|
|
637
|
+
async verify(proof, from) {
|
|
585
638
|
// We cannot apply "other forks" atm.
|
|
586
639
|
// We should probably still try and they are likely super similar for non upgrades
|
|
587
640
|
// but this is easy atm (and the above layer will just retry)
|
|
@@ -616,7 +669,7 @@ module.exports = class Core {
|
|
|
616
669
|
return this._verified
|
|
617
670
|
}
|
|
618
671
|
|
|
619
|
-
async reorg
|
|
672
|
+
async reorg(batch) {
|
|
620
673
|
if (!batch.commitable()) return false
|
|
621
674
|
|
|
622
675
|
this.truncating++
|
|
@@ -630,7 +683,7 @@ module.exports = class Core {
|
|
|
630
683
|
return true
|
|
631
684
|
}
|
|
632
685
|
|
|
633
|
-
openSkipBitfield
|
|
686
|
+
openSkipBitfield() {
|
|
634
687
|
if (this.skipBitfield !== null) return this.skipBitfield
|
|
635
688
|
this.skipBitfield = new RemoteBitfield()
|
|
636
689
|
const buf = this.bitfield.toBuffer(this.state.length)
|
|
@@ -639,17 +692,17 @@ module.exports = class Core {
|
|
|
639
692
|
return this.skipBitfield
|
|
640
693
|
}
|
|
641
694
|
|
|
642
|
-
_setBitfieldRanges
|
|
695
|
+
_setBitfieldRanges(start, end, value) {
|
|
643
696
|
this.bitfield.setRange(start, end, value)
|
|
644
697
|
if (this.skipBitfield !== null) this.skipBitfield.setRange(start, end, value)
|
|
645
698
|
}
|
|
646
699
|
|
|
647
|
-
close
|
|
700
|
+
close() {
|
|
648
701
|
if (!this.closing) this.closing = this._close()
|
|
649
702
|
return this.closing
|
|
650
703
|
}
|
|
651
704
|
|
|
652
|
-
updateContiguousLength
|
|
705
|
+
updateContiguousLength(bitfield) {
|
|
653
706
|
const contig = updateContigBatch(this.header.hints.contiguousLength, bitfield, this.bitfield)
|
|
654
707
|
|
|
655
708
|
if (contig.length !== -1 && contig.length !== this.header.hints.contiguousLength) {
|
|
@@ -657,7 +710,7 @@ module.exports = class Core {
|
|
|
657
710
|
}
|
|
658
711
|
}
|
|
659
712
|
|
|
660
|
-
onappend
|
|
713
|
+
onappend(tree, bitfield) {
|
|
661
714
|
this.header.tree = tree
|
|
662
715
|
|
|
663
716
|
if (!bitfield) {
|
|
@@ -677,7 +730,7 @@ module.exports = class Core {
|
|
|
677
730
|
this.replicator.uncork()
|
|
678
731
|
}
|
|
679
732
|
|
|
680
|
-
ontruncate
|
|
733
|
+
ontruncate(tree, { start, length }) {
|
|
681
734
|
if (tree) this.header.tree = tree
|
|
682
735
|
|
|
683
736
|
this.replicator.cork()
|
|
@@ -695,7 +748,7 @@ module.exports = class Core {
|
|
|
695
748
|
this.updateContiguousLength({ start, length, drop: true })
|
|
696
749
|
}
|
|
697
750
|
|
|
698
|
-
async _onconflict
|
|
751
|
+
async _onconflict(proof) {
|
|
699
752
|
await this.replicator.onconflict()
|
|
700
753
|
|
|
701
754
|
for (let i = this.monitors.length - 1; i >= 0; i--) {
|
|
@@ -707,7 +760,7 @@ module.exports = class Core {
|
|
|
707
760
|
await this.closeAllSessions(err)
|
|
708
761
|
}
|
|
709
762
|
|
|
710
|
-
async closeAllSessions
|
|
763
|
+
async closeAllSessions(err) {
|
|
711
764
|
// this.sessions modifies itself when a session closes
|
|
712
765
|
// This way we ensure we indeed iterate over all sessions
|
|
713
766
|
const sessions = this.allSessions()
|
|
@@ -717,7 +770,7 @@ module.exports = class Core {
|
|
|
717
770
|
await Promise.allSettled(all)
|
|
718
771
|
}
|
|
719
772
|
|
|
720
|
-
async destroy
|
|
773
|
+
async destroy() {
|
|
721
774
|
if (this.destroyed === true) return
|
|
722
775
|
this.destroyed = true
|
|
723
776
|
|
|
@@ -732,7 +785,7 @@ module.exports = class Core {
|
|
|
732
785
|
for (const s of weakSessions) s.close().catch(noop)
|
|
733
786
|
}
|
|
734
787
|
|
|
735
|
-
async _close
|
|
788
|
+
async _close() {
|
|
736
789
|
if (this.opened === false) await this.opening
|
|
737
790
|
if (this.hasSession() === true) throw new Error('Cannot close while sessions are open')
|
|
738
791
|
|
|
@@ -745,7 +798,7 @@ module.exports = class Core {
|
|
|
745
798
|
}
|
|
746
799
|
}
|
|
747
800
|
|
|
748
|
-
function updateContigBatch
|
|
801
|
+
function updateContigBatch(start, upd, bitfield) {
|
|
749
802
|
const end = upd.start + upd.length
|
|
750
803
|
|
|
751
804
|
let c = start
|
|
@@ -779,7 +832,7 @@ function updateContigBatch (start, upd, bitfield) {
|
|
|
779
832
|
}
|
|
780
833
|
}
|
|
781
834
|
|
|
782
|
-
function getDefaultTree
|
|
835
|
+
function getDefaultTree() {
|
|
783
836
|
return {
|
|
784
837
|
fork: 0,
|
|
785
838
|
length: 0,
|
|
@@ -788,7 +841,7 @@ function getDefaultTree () {
|
|
|
788
841
|
}
|
|
789
842
|
}
|
|
790
843
|
|
|
791
|
-
function parseHeader
|
|
844
|
+
function parseHeader(info) {
|
|
792
845
|
if (!info) return null
|
|
793
846
|
|
|
794
847
|
return {
|
|
@@ -804,9 +857,9 @@ function parseHeader (info) {
|
|
|
804
857
|
}
|
|
805
858
|
}
|
|
806
859
|
|
|
807
|
-
function noop
|
|
860
|
+
function noop() {}
|
|
808
861
|
|
|
809
|
-
async function getCoreInfo
|
|
862
|
+
async function getCoreInfo(storage) {
|
|
810
863
|
const r = storage.read()
|
|
811
864
|
|
|
812
865
|
const auth = r.getAuth()
|
|
@@ -8,7 +8,7 @@ const nonce = b4a.alloc(sodium.crypto_stream_NONCEBYTES)
|
|
|
8
8
|
module.exports = class DefaultEncryption {
|
|
9
9
|
static PADDING = 8
|
|
10
10
|
|
|
11
|
-
constructor
|
|
11
|
+
constructor(encryptionKey, hypercoreKey, opts = {}) {
|
|
12
12
|
this.key = encryptionKey
|
|
13
13
|
this.compat = opts.compat === true
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@ module.exports = class DefaultEncryption {
|
|
|
18
18
|
this.blindingKey = keys.blinding
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
static deriveKeys
|
|
21
|
+
static deriveKeys(encryptionKey, hypercoreKey, { block = false, compat = false } = {}) {
|
|
22
22
|
const subKeys = b4a.alloc(2 * sodium.crypto_stream_KEYBYTES)
|
|
23
23
|
|
|
24
24
|
const blockKey = block ? encryptionKey : subKeys.subarray(0, sodium.crypto_stream_KEYBYTES)
|
|
@@ -26,7 +26,8 @@ module.exports = class DefaultEncryption {
|
|
|
26
26
|
|
|
27
27
|
if (!block) {
|
|
28
28
|
if (compat) sodium.crypto_generichash_batch(blockKey, [encryptionKey], hypercoreKey)
|
|
29
|
-
else
|
|
29
|
+
else
|
|
30
|
+
sodium.crypto_generichash_batch(blockKey, [DEFAULT_ENCRYPTION, hypercoreKey, encryptionKey])
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
sodium.crypto_generichash(blindingKey, blockKey)
|
|
@@ -37,13 +38,13 @@ module.exports = class DefaultEncryption {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
static blockEncryptionKey
|
|
41
|
+
static blockEncryptionKey(hypercoreKey, encryptionKey) {
|
|
41
42
|
const blockKey = b4a.alloc(sodium.crypto_stream_KEYBYTES)
|
|
42
43
|
sodium.crypto_generichash_batch(blockKey, [DEFAULT_ENCRYPTION, hypercoreKey, encryptionKey])
|
|
43
44
|
return blockKey
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
static encrypt
|
|
47
|
+
static encrypt(index, block, fork, blockKey, blindingKey) {
|
|
47
48
|
const padding = block.subarray(0, DefaultEncryption.PADDING)
|
|
48
49
|
block = block.subarray(DefaultEncryption.PADDING)
|
|
49
50
|
|
|
@@ -56,26 +57,16 @@ module.exports = class DefaultEncryption {
|
|
|
56
57
|
// Blind the fork ID, possibly risking reusing the nonce on a reorg of the
|
|
57
58
|
// Hypercore. This is fine as the blinding is best-effort and the latest
|
|
58
59
|
// fork ID shared on replication anyway.
|
|
59
|
-
sodium.crypto_stream_xor(
|
|
60
|
-
padding,
|
|
61
|
-
padding,
|
|
62
|
-
nonce,
|
|
63
|
-
blindingKey
|
|
64
|
-
)
|
|
60
|
+
sodium.crypto_stream_xor(padding, padding, nonce, blindingKey)
|
|
65
61
|
|
|
66
62
|
nonce.set(padding, 8)
|
|
67
63
|
|
|
68
64
|
// The combination of a (blinded) fork ID and a block index is unique for a
|
|
69
65
|
// given Hypercore and is therefore a valid nonce for encrypting the block.
|
|
70
|
-
sodium.crypto_stream_xor(
|
|
71
|
-
block,
|
|
72
|
-
block,
|
|
73
|
-
nonce,
|
|
74
|
-
blockKey
|
|
75
|
-
)
|
|
66
|
+
sodium.crypto_stream_xor(block, block, nonce, blockKey)
|
|
76
67
|
}
|
|
77
68
|
|
|
78
|
-
static decrypt
|
|
69
|
+
static decrypt(index, block, blockKey) {
|
|
79
70
|
const padding = block.subarray(0, DefaultEncryption.PADDING)
|
|
80
71
|
block = block.subarray(DefaultEncryption.PADDING)
|
|
81
72
|
|
|
@@ -84,29 +75,24 @@ module.exports = class DefaultEncryption {
|
|
|
84
75
|
nonce.set(padding, 8)
|
|
85
76
|
|
|
86
77
|
// Decrypt the block using the blinded fork ID.
|
|
87
|
-
sodium.crypto_stream_xor(
|
|
88
|
-
block,
|
|
89
|
-
block,
|
|
90
|
-
nonce,
|
|
91
|
-
blockKey
|
|
92
|
-
)
|
|
78
|
+
sodium.crypto_stream_xor(block, block, nonce, blockKey)
|
|
93
79
|
}
|
|
94
80
|
|
|
95
|
-
encrypt
|
|
81
|
+
encrypt(index, block, fork, core) {
|
|
96
82
|
if (core.compat !== this.compat) this._reload(core)
|
|
97
83
|
return DefaultEncryption.encrypt(index, block, fork, this.blockKey, this.blindingKey)
|
|
98
84
|
}
|
|
99
85
|
|
|
100
|
-
decrypt
|
|
86
|
+
decrypt(index, block, core) {
|
|
101
87
|
if (core.compat !== this.compat) this._reload(core)
|
|
102
88
|
return DefaultEncryption.decrypt(index, block, this.blockKey)
|
|
103
89
|
}
|
|
104
90
|
|
|
105
|
-
padding
|
|
91
|
+
padding() {
|
|
106
92
|
return DefaultEncryption.PADDING
|
|
107
93
|
}
|
|
108
94
|
|
|
109
|
-
_reload
|
|
95
|
+
_reload(core) {
|
|
110
96
|
const block = b4a.equals(this.key, this.blockKey)
|
|
111
97
|
const keys = DefaultEncryption.deriveKeys(this.key, core.key, { block, compat: core.compat })
|
|
112
98
|
|
package/lib/download.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module.exports = class Download {
|
|
2
|
-
constructor
|
|
2
|
+
constructor(session, range) {
|
|
3
3
|
this.session = session
|
|
4
4
|
this.range = range
|
|
5
5
|
this.request = null
|
|
@@ -8,17 +8,17 @@ module.exports = class Download {
|
|
|
8
8
|
this.opening.catch(noop)
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
ready
|
|
11
|
+
ready() {
|
|
12
12
|
return this.opening
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
async _open
|
|
15
|
+
async _open() {
|
|
16
16
|
if (this.session.opened === false) await this.session.opening
|
|
17
17
|
this._download()
|
|
18
18
|
this.opened = true
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
async done
|
|
21
|
+
async done() {
|
|
22
22
|
await this.ready()
|
|
23
23
|
|
|
24
24
|
try {
|
|
@@ -29,7 +29,7 @@ module.exports = class Download {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
_download
|
|
32
|
+
_download() {
|
|
33
33
|
const activeRequests = (this.range && this.range.activeRequests) || this.session.activeRequests
|
|
34
34
|
this.request = this.session.core.replicator.addRange(activeRequests, this.range)
|
|
35
35
|
this.request.promise.catch(noop)
|
|
@@ -39,22 +39,22 @@ module.exports = class Download {
|
|
|
39
39
|
/**
|
|
40
40
|
* Deprecated. Use `range.done()`.
|
|
41
41
|
*/
|
|
42
|
-
downloaded
|
|
42
|
+
downloaded() {
|
|
43
43
|
return this.done()
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
destroy
|
|
46
|
+
destroy() {
|
|
47
47
|
this._destroyBackground().catch(noop)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
async _destroyBackground
|
|
50
|
+
async _destroyBackground() {
|
|
51
51
|
if (this.opened === false) await this.ready()
|
|
52
52
|
if (this.request.context) this.request.context.detach(this.request)
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function noop
|
|
56
|
+
function noop() {}
|
|
57
57
|
|
|
58
|
-
function isSessionMoved
|
|
58
|
+
function isSessionMoved(err) {
|
|
59
59
|
return err.code === 'SESSION_MOVED'
|
|
60
60
|
}
|
|
@@ -8,7 +8,7 @@ const crypto = require('hypercore-crypto')
|
|
|
8
8
|
const flat = require('flat-tree')
|
|
9
9
|
|
|
10
10
|
class SlimSession {
|
|
11
|
-
constructor
|
|
11
|
+
constructor(storage, auth, head, roots) {
|
|
12
12
|
this.fork = head ? head.fork : 0
|
|
13
13
|
this.roots = roots
|
|
14
14
|
this.length = head ? head.length : 0
|
|
@@ -24,7 +24,7 @@ class SlimSession {
|
|
|
24
24
|
|
|
25
25
|
module.exports = { verify, proof }
|
|
26
26
|
|
|
27
|
-
async function verify
|
|
27
|
+
async function verify(storage, buffer, { referrer = null } = {}) {
|
|
28
28
|
const state = { buffer, start: 0, end: buffer.byteLength }
|
|
29
29
|
|
|
30
30
|
const discoveryKey = c.fixed32.decode(state)
|
|
@@ -100,7 +100,7 @@ async function verify (storage, buffer, { referrer = null } = {}) {
|
|
|
100
100
|
return result
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
async function proof
|
|
103
|
+
async function proof(sender, { index, block = null } = {}) {
|
|
104
104
|
const treeProof = await sender.proof({
|
|
105
105
|
block: block ? { index, nodes: 0 } : null,
|
|
106
106
|
upgrade: { start: 0, length: sender.length }
|