hypercore 10.0.0-alpha.2 → 10.0.0-alpha.23

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.
@@ -0,0 +1,68 @@
1
+ const sodium = require('sodium-universal')
2
+ const c = require('compact-encoding')
3
+ const b4a = require('b4a')
4
+
5
+ const nonce = b4a.alloc(sodium.crypto_stream_NONCEBYTES)
6
+
7
+ module.exports = class BlockEncryption {
8
+ constructor (encryptionKey, hypercoreKey) {
9
+ const subKeys = b4a.alloc(2 * sodium.crypto_stream_KEYBYTES)
10
+
11
+ this.key = encryptionKey
12
+ this.blockKey = subKeys.subarray(0, sodium.crypto_stream_KEYBYTES)
13
+ this.blindingKey = subKeys.subarray(sodium.crypto_stream_KEYBYTES)
14
+ this.padding = 8
15
+
16
+ sodium.crypto_generichash(this.blockKey, encryptionKey, hypercoreKey)
17
+ sodium.crypto_generichash(this.blindingKey, this.blockKey)
18
+ }
19
+
20
+ encrypt (index, block, fork) {
21
+ const padding = block.subarray(0, this.padding)
22
+ block = block.subarray(this.padding)
23
+
24
+ c.uint64.encode({ start: 0, end: 8, buffer: padding }, fork)
25
+ c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)
26
+
27
+ // Zero out any previous padding.
28
+ nonce.fill(0, 8, 8 + padding.byteLength)
29
+
30
+ // Blind the fork ID, possibly risking reusing the nonce on a reorg of the
31
+ // Hypercore. This is fine as the blinding is best-effort and the latest
32
+ // fork ID shared on replication anyway.
33
+ sodium.crypto_stream_xor(
34
+ padding,
35
+ padding,
36
+ nonce,
37
+ this.blindingKey
38
+ )
39
+
40
+ nonce.set(padding, 8)
41
+
42
+ // The combination of a (blinded) fork ID and a block index is unique for a
43
+ // given Hypercore and is therefore a valid nonce for encrypting the block.
44
+ sodium.crypto_stream_xor(
45
+ block,
46
+ block,
47
+ nonce,
48
+ this.blockKey
49
+ )
50
+ }
51
+
52
+ decrypt (index, block) {
53
+ const padding = block.subarray(0, this.padding)
54
+ block = block.subarray(this.padding)
55
+
56
+ c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)
57
+
58
+ nonce.set(padding, 8)
59
+
60
+ // Decrypt the block using the blinded fork ID.
61
+ sodium.crypto_stream_xor(
62
+ block,
63
+ block,
64
+ nonce,
65
+ this.blockKey
66
+ )
67
+ }
68
+ }
@@ -1,3 +1,5 @@
1
+ const b4a = require('b4a')
2
+
1
3
  module.exports = class BlockStore {
2
4
  constructor (storage, tree) {
3
5
  this.storage = storage
@@ -15,7 +17,7 @@ module.exports = class BlockStore {
15
17
 
16
18
  putBatch (i, batch, offset) {
17
19
  if (batch.length === 0) return Promise.resolve()
18
- return this.put(i, batch.length === 1 ? batch[0] : Buffer.concat(batch), offset)
20
+ return this.put(i, batch.length === 1 ? batch[0] : b4a.concat(batch), offset)
19
21
  }
20
22
 
21
23
  clear () {
package/lib/core.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const hypercoreCrypto = require('hypercore-crypto')
2
+ const b4a = require('b4a')
2
3
  const Oplog = require('./oplog')
3
4
  const Mutex = require('./mutex')
4
5
  const MerkleTree = require('./merkle-tree')
@@ -90,7 +91,7 @@ module.exports = class Core {
90
91
  await oplog.flush(header)
91
92
  }
92
93
 
93
- if (opts.keyPair && !header.signer.publicKey.equals(opts.keyPair.publicKey)) {
94
+ if (opts.keyPair && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
94
95
  throw new Error('Another hypercore is stored here')
95
96
  }
96
97
 
@@ -176,7 +177,7 @@ module.exports = class Core {
176
177
 
177
178
  for (const u of this.header.userData) {
178
179
  if (u.key !== key) continue
179
- if (value && u.value.equals(value)) return
180
+ if (value && b4a.equals(u.value, value)) return
180
181
  empty = false
181
182
  break
182
183
  }
@@ -214,10 +215,12 @@ module.exports = class Core {
214
215
  }
215
216
  }
216
217
 
217
- async append (values, sign = this.defaultSign) {
218
+ async append (values, sign = this.defaultSign, hooks = {}) {
218
219
  await this._mutex.lock()
219
220
 
220
221
  try {
222
+ if (hooks.preappend) await hooks.preappend(values)
223
+
221
224
  if (!values.length) return this.tree.length
222
225
 
223
226
  const batch = this.tree.batch()
@@ -1,9 +1,10 @@
1
1
  const flat = require('flat-tree')
2
2
  const crypto = require('hypercore-crypto')
3
- const uint64le = require('uint64le')
3
+ const c = require('compact-encoding')
4
+ const b4a = require('b4a')
4
5
 
5
- const BLANK_HASH = Buffer.alloc(32)
6
- const OLD_TREE = Buffer.from([5, 2, 87, 2, 0, 0, 40, 7, 66, 76, 65, 75, 69, 50, 98])
6
+ const BLANK_HASH = b4a.alloc(32)
7
+ const OLD_TREE = b4a.from([5, 2, 87, 2, 0, 0, 40, 7, 66, 76, 65, 75, 69, 50, 98])
7
8
 
8
9
  class NodeQueue {
9
10
  constructor (nodes, extra = null) {
@@ -214,7 +215,7 @@ class ReorgBatch extends MerkleTreeBatch {
214
215
  const nodes = []
215
216
  const root = verifyBlock(proof, this.tree.crypto, nodes)
216
217
 
217
- if (root === null || !root.hash.equals(this.diff.hash)) return false
218
+ if (root === null || !b4a.equals(root.hash, this.diff.hash)) return false
218
219
 
219
220
  this.nodes.push(...nodes)
220
221
  return this._update(nodes)
@@ -232,7 +233,7 @@ class ReorgBatch extends MerkleTreeBatch {
232
233
  if (!left) break
233
234
 
234
235
  const existing = await this.tree.get(left.index, false)
235
- if (!existing || !existing.hash.equals(left.hash)) {
236
+ if (!existing || !b4a.equals(existing.hash, left.hash)) {
236
237
  diff = left
237
238
  } else {
238
239
  diff = n.get(ite.sibling())
@@ -246,6 +247,8 @@ class ReorgBatch extends MerkleTreeBatch {
246
247
  }
247
248
 
248
249
  _updateDiffRoot (diff) {
250
+ if (this.want === null) return true
251
+
249
252
  const spans = flat.spans(diff.index)
250
253
  const start = spans[0] / 2
251
254
  const end = Math.min(this.treeLength, spans[1] / 2 + 1)
@@ -272,11 +275,15 @@ class ReorgBatch extends MerkleTreeBatch {
272
275
  }
273
276
 
274
277
  class ByteSeeker {
275
- constructor (tree, bytes) {
278
+ constructor (tree, bytes, padding = 0) {
276
279
  this.tree = tree
277
280
  this.bytes = bytes
278
- this.start = bytes >= tree.byteLength ? tree.length : 0
279
- this.end = bytes < tree.byteLength ? tree.length : 0
281
+ this.padding = padding
282
+
283
+ const size = tree.byteLength - (tree.length * padding)
284
+
285
+ this.start = bytes >= size ? tree.length : 0
286
+ this.end = bytes < size ? tree.length : 0
280
287
  }
281
288
 
282
289
  nodes () {
@@ -287,12 +294,12 @@ class ByteSeeker {
287
294
  if (!bytes) return [0, 0]
288
295
 
289
296
  for (const node of this.tree.roots) { // all async ticks happen once we find the root so safe
290
- if (bytes === node.size) {
291
- return [flat.rightSpan(node.index) + 2, 0]
292
- }
297
+ let size = node.size
298
+ if (this.padding > 0) size -= this.padding * flat.countLeaves(node.index)
293
299
 
294
- if (bytes > node.size) {
295
- bytes -= node.size
300
+ if (bytes === size) return [flat.rightSpan(node.index) + 2, 0]
301
+ if (bytes > size) {
302
+ bytes -= size
296
303
  continue
297
304
  }
298
305
 
@@ -301,9 +308,12 @@ class ByteSeeker {
301
308
  while ((ite.index & 1) !== 0) {
302
309
  const l = await this.tree.get(ite.leftChild(), false)
303
310
  if (l) {
304
- if (l.size === bytes) return [ite.rightSpan() + 2, 0]
305
- if (l.size > bytes) continue
306
- bytes -= l.size
311
+ let size = l.size
312
+ if (this.padding > 0) size -= this.padding * ite.countLeaves()
313
+
314
+ if (size === bytes) return [ite.rightSpan() + 2, 0]
315
+ if (size > bytes) continue
316
+ bytes -= size
307
317
  ite.sibling()
308
318
  } else {
309
319
  ite.parent()
@@ -347,7 +357,7 @@ module.exports = class MerkleTree {
347
357
  }
348
358
 
349
359
  addNode (node) {
350
- if (node.size === 0 && node.hash.equals(BLANK_HASH)) node = blankNode(node.index)
360
+ if (node.size === 0 && b4a.equals(node.hash, BLANK_HASH)) node = blankNode(node.index)
351
361
  this.unflushed.set(node.index, node)
352
362
  }
353
363
 
@@ -355,8 +365,8 @@ module.exports = class MerkleTree {
355
365
  return new MerkleTreeBatch(this)
356
366
  }
357
367
 
358
- seek (bytes) {
359
- return new ByteSeeker(this, bytes)
368
+ seek (bytes, padding) {
369
+ return new ByteSeeker(this, bytes, padding)
360
370
  }
361
371
 
362
372
  hash () {
@@ -371,6 +381,17 @@ module.exports = class MerkleTree {
371
381
  return this.signature !== null && this.crypto.verify(this.signable(), this.signature, key)
372
382
  }
373
383
 
384
+ getRoots (length) {
385
+ const indexes = flat.fullRoots(2 * length)
386
+ const roots = new Array(indexes.length)
387
+
388
+ for (let i = 0; i < indexes.length; i++) {
389
+ roots[i] = this.get(indexes[i], true)
390
+ }
391
+
392
+ return Promise.all(roots)
393
+ }
394
+
374
395
  get (index, error = true) {
375
396
  let node = this.unflushed.get(index)
376
397
 
@@ -433,17 +454,23 @@ module.exports = class MerkleTree {
433
454
  // TODO: write neighbors together etc etc
434
455
  // TODO: bench loading a full disk page and copy to that instead
435
456
  return new Promise((resolve, reject) => {
436
- const slab = Buffer.allocUnsafe(40 * this.flushing.size)
457
+ const slab = b4a.allocUnsafe(40 * this.flushing.size)
437
458
 
438
459
  let error = null
439
460
  let missing = this.flushing.size + 1
440
461
  let offset = 0
441
462
 
442
463
  for (const node of this.flushing.values()) {
443
- const b = slab.slice(offset, offset += 40)
444
- uint64le.encode(node.size, b, 0)
445
- node.hash.copy(b, 8)
446
- this.storage.write(node.index * 40, b, done)
464
+ const state = {
465
+ start: 0,
466
+ end: 40,
467
+ buffer: slab.subarray(offset, offset += 40)
468
+ }
469
+
470
+ c.uint64.encode(state, node.size)
471
+ c.raw.encode(state, node.hash)
472
+
473
+ this.storage.write(node.index * 40, state.buffer, done)
447
474
  }
448
475
 
449
476
  done(null)
@@ -520,7 +547,7 @@ module.exports = class MerkleTree {
520
547
 
521
548
  for (const root of batch.roots) {
522
549
  const existing = await this.get(root.index, false)
523
- if (existing && existing.hash.equals(root.hash)) continue
550
+ if (existing && b4a.equals(existing.hash, root.hash)) continue
524
551
  batch._updateDiffRoot(root)
525
552
  break
526
553
  }
@@ -548,7 +575,7 @@ module.exports = class MerkleTree {
548
575
 
549
576
  if (unverified) {
550
577
  const verified = await this.get(unverified.index)
551
- if (!verified.hash.equals(unverified.hash)) {
578
+ if (!b4a.equals(verified.hash, unverified.hash)) {
552
579
  throw new Error('Invalid checksum at node ' + unverified.index)
553
580
  }
554
581
  }
@@ -693,7 +720,7 @@ module.exports = class MerkleTree {
693
720
  await new Promise((resolve, reject) => {
694
721
  storage.read(0, OLD_TREE.length, (err, buf) => {
695
722
  if (err) return resolve()
696
- if (buf.equals(OLD_TREE)) return reject(new Error('Storage contains an incompatible merkle tree'))
723
+ if (b4a.equals(buf, OLD_TREE)) return reject(new Error('Storage contains an incompatible merkle tree'))
697
724
  resolve()
698
725
  })
699
726
  })
@@ -1038,10 +1065,10 @@ function getStoredNode (storage, index, error) {
1038
1065
  return
1039
1066
  }
1040
1067
 
1041
- const hash = data.slice(8)
1042
- const size = uint64le.decode(data, 0)
1068
+ const hash = data.subarray(8)
1069
+ const size = c.decode(c.uint64, data)
1043
1070
 
1044
- if (size === 0 && Buffer.compare(hash, BLANK_HASH) === 0) {
1071
+ if (size === 0 && b4a.compare(hash, BLANK_HASH) === 0) {
1045
1072
  if (error) reject(new Error('Could not load node: ' + index))
1046
1073
  else resolve(null)
1047
1074
  return
@@ -1088,9 +1115,9 @@ function log2 (n) {
1088
1115
  }
1089
1116
 
1090
1117
  function signable (hash, length, fork) {
1091
- const buf = Buffer.alloc(48)
1092
- hash.copy(buf)
1093
- uint64le.encode(length, buf, 32)
1094
- uint64le.encode(fork, buf, 40)
1095
- return buf
1118
+ const state = { start: 0, end: 48, buffer: b4a.alloc(48) }
1119
+ c.raw.encode(state, hash)
1120
+ c.uint64.encode(state, length)
1121
+ c.uint64.encode(state, fork)
1122
+ return state.buffer
1096
1123
  }
package/lib/oplog.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const cenc = require('compact-encoding')
2
+ const b4a = require('b4a')
2
3
  const crc32 = require('crc32-universal')
3
4
 
4
5
  module.exports = class Oplog {
@@ -133,7 +134,7 @@ module.exports = class Oplog {
133
134
  return new Promise((resolve, reject) => {
134
135
  this.storage.open(err => {
135
136
  if (err && err.code !== 'ENOENT') return reject(err)
136
- if (err) return resolve(Buffer.alloc(0))
137
+ if (err) return resolve(b4a.alloc(0))
137
138
  this.storage.stat((err, stat) => {
138
139
  if (err && err.code !== 'ENOENT') return reject(err)
139
140
  this.storage.read(0, stat.size, (err, buf) => {
@@ -151,7 +152,7 @@ module.exports = class Oplog {
151
152
  const bit = (this._headers[i] + 1) & 1
152
153
 
153
154
  this.headerEncoding.preencode(state, header)
154
- state.buffer = Buffer.allocUnsafe(state.end)
155
+ state.buffer = b4a.allocUnsafe(state.end)
155
156
  this.headerEncoding.encode(state, header)
156
157
  this._addHeader(state, state.end - 8, bit, 0)
157
158
 
@@ -187,7 +188,7 @@ module.exports = class Oplog {
187
188
  this.entryEncoding.preencode(state, batch[i])
188
189
  }
189
190
 
190
- state.buffer = Buffer.allocUnsafe(state.end)
191
+ state.buffer = b4a.allocUnsafe(state.end)
191
192
 
192
193
  for (let i = 0; i < batch.length; i++) {
193
194
  const start = state.start += 8 // space for header
package/lib/protocol.js CHANGED
@@ -1,9 +1,20 @@
1
1
  const { uint, from: fromEncoding } = require('compact-encoding')
2
+ const b4a = require('b4a')
2
3
  const safetyCatch = require('safety-catch')
3
4
  const codecs = require('codecs')
5
+ const sodium = require('sodium-universal')
4
6
 
5
7
  const messages = require('./messages')
6
8
 
9
+ const slab = b4a.alloc(96)
10
+ const NS = slab.subarray(0, 32)
11
+ const NS_HYPERCORE_INITIATOR = slab.subarray(32, 64)
12
+ const NS_HYPERCORE_RESPONDER = slab.subarray(64, 96)
13
+
14
+ sodium.crypto_generichash(NS, b4a.from('hypercore'))
15
+ sodium.crypto_generichash(NS_HYPERCORE_INITIATOR, b4a.from([0]), NS)
16
+ sodium.crypto_generichash(NS_HYPERCORE_RESPONDER, b4a.from([1]), NS)
17
+
7
18
  class Extension {
8
19
  constructor (protocol, type, name, handlers) {
9
20
  this.protocol = protocol
@@ -38,7 +49,7 @@ class Extension {
38
49
  _sendAlias (message, alias) {
39
50
  if (this.destroyed) return
40
51
 
41
- if (this._remoteAliases) {
52
+ if (this.remoteSupports) {
42
53
  return this.protocol.send(this.type, this.encoding, alias, message)
43
54
  }
44
55
 
@@ -125,6 +136,7 @@ class Peer {
125
136
  this.resend = false
126
137
  this.state = state
127
138
  this.extensions = new Map()
139
+ this.destroyed = false
128
140
 
129
141
  this._destroyer = this._safeDestroy.bind(this)
130
142
  }
@@ -229,6 +241,7 @@ class Peer {
229
241
  }
230
242
 
231
243
  destroy (err) {
244
+ this.destroyed = true
232
245
  return this.protocol.unregisterPeer(this, err)
233
246
  }
234
247
  }
@@ -253,11 +266,17 @@ module.exports = class Protocol {
253
266
  this._localExtensions = 128
254
267
  this._remoteExtensions = []
255
268
  this._extensions = new Map()
269
+ this._keepAliveInterval = null
270
+ this._pendingCaps = []
256
271
 
257
272
  this._destroyer = this._safeDestroy.bind(this)
258
273
  this.noiseStream.on('data', this.onmessage.bind(this))
259
274
  this.noiseStream.on('end', this.noiseStream.end) // no half open
275
+ this.noiseStream.on('finish', () => {
276
+ this.setKeepAlive(false)
277
+ })
260
278
  this.noiseStream.on('close', () => {
279
+ this.setKeepAlive(false)
261
280
  // TODO: If the stream was destroyed with an error, we probably want to forward it here
262
281
  for (const peer of this._peers.values()) {
263
282
  peer.destroy(null)
@@ -267,6 +286,18 @@ module.exports = class Protocol {
267
286
  this._sendHandshake()
268
287
  }
269
288
 
289
+ setKeepAlive (enable) {
290
+ if (enable) {
291
+ if (this._keepAliveInterval) return
292
+ this._keepAliveInterval = setInterval(this.ping.bind(this), 5000)
293
+ if (this._keepAliveInterval.unref) this._keepAliveInterval.unref()
294
+ } else {
295
+ if (!this._keepAliveInterval) return
296
+ clearInterval(this._keepAliveInterval)
297
+ this._keepAliveInterval = null
298
+ }
299
+ }
300
+
270
301
  _sendHandshake () {
271
302
  const m = { protocolVersion: this.protocolVersion, userAgent: this.userAgent }
272
303
  const state = { start: 0, end: 0, buffer: null }
@@ -283,20 +314,20 @@ module.exports = class Protocol {
283
314
 
284
315
  registerPeer (key, discoveryKey, handlers = {}, state = null) {
285
316
  const peer = new Peer(this, this._localAliases++, key, discoveryKey, handlers, state)
286
- this._peers.set(discoveryKey.toString('hex'), peer)
317
+ this._peers.set(b4a.toString(discoveryKey, 'hex'), peer)
287
318
  this._announceCore(peer.alias, key, discoveryKey)
288
319
  return peer
289
320
  }
290
321
 
291
322
  unregisterPeer (peer, err) {
292
- this._peers.delete(peer.discoveryKey.toString('hex'))
323
+ this._peers.delete(b4a.toString(peer.discoveryKey, 'hex'))
293
324
 
294
325
  if (peer.remoteAlias > -1) {
295
326
  this._remoteAliases[peer.remoteAlias] = null
296
327
  peer.remoteAlias = -1
297
328
  }
298
329
 
299
- peer.handlers.onunregister(this, err)
330
+ peer.handlers.onunregister(peer, err)
300
331
 
301
332
  if (err) this.noiseStream.destroy(err)
302
333
  }
@@ -329,6 +360,11 @@ module.exports = class Protocol {
329
360
 
330
361
  if (batch.length === 0) return
331
362
 
363
+ while (this._pendingCaps.length > 0) {
364
+ const [key, cap] = this._pendingCaps.pop()
365
+ hypercoreCapability(this.noiseStream.isInitiator, this.noiseStream.handshakeHash, key, cap)
366
+ }
367
+
332
368
  const state = { start: 0, end: 0, buffer: null }
333
369
  const lens = new Array(batch.length)
334
370
 
@@ -369,14 +405,24 @@ module.exports = class Protocol {
369
405
  }
370
406
 
371
407
  _announceCore (alias, key, discoveryKey) {
408
+ const cap = b4a.alloc(32)
409
+
410
+ if (!this.noiseStream.handshakeHash) {
411
+ this._pendingCaps.push([key, cap]) // encode it later...
412
+ } else {
413
+ hypercoreCapability(this.noiseStream.isInitiator, this.noiseStream.handshakeHash, key, cap)
414
+ }
415
+
372
416
  this.send(2, messages.core, -1, {
373
417
  alias: alias,
374
418
  discoveryKey: discoveryKey,
375
- capability: Buffer.alloc(32) // TODO
419
+ capability: cap
376
420
  })
377
421
  }
378
422
 
379
423
  _decode (buffer) {
424
+ if (buffer.byteLength === 0) return
425
+
380
426
  const state = { start: 0, end: buffer.length, buffer }
381
427
 
382
428
  if (this._firstMessage === true) {
@@ -441,7 +487,7 @@ module.exports = class Protocol {
441
487
  }
442
488
 
443
489
  _oncore (m) {
444
- const hex = m.discoveryKey.toString('hex')
490
+ const hex = b4a.toString(m.discoveryKey, 'hex')
445
491
  const peer = this._peers.get(hex)
446
492
 
447
493
  // allow one alloc
@@ -450,7 +496,11 @@ module.exports = class Protocol {
450
496
  if (m.alias === this._remoteAliases.length) this._remoteAliases.push(null)
451
497
 
452
498
  if (peer) {
453
- // TODO: check cap
499
+ const expectedCap = hypercoreCapability(!this.noiseStream.isInitiator, this.noiseStream.handshakeHash, peer.key)
500
+ if (!b4a.equals(expectedCap, m.capability)) {
501
+ this.destroy(new Error('Remote sent an invalid capability'))
502
+ return
503
+ }
454
504
 
455
505
  if (m.alias >= this._remoteAliases.length) {
456
506
  this.destroy(new Error('Remote alias out of bounds'))
@@ -477,7 +527,7 @@ module.exports = class Protocol {
477
527
  }
478
528
 
479
529
  _onunknowncore (m) {
480
- const peer = this._peers.get(m.discoveryKey.toString('hex'))
530
+ const peer = this._peers.get(b4a.toString(m.discoveryKey, 'hex'))
481
531
  if (!peer) return
482
532
 
483
533
  peer.resend = true
@@ -505,6 +555,11 @@ module.exports = class Protocol {
505
555
  return this.noiseStream.write(state.buffer)
506
556
  }
507
557
 
558
+ ping () {
559
+ const empty = this.noiseStream.alloc(0)
560
+ this.noiseStream.write(empty)
561
+ }
562
+
508
563
  destroy (err) {
509
564
  return this.noiseStream.destroy(err)
510
565
  }
@@ -520,3 +575,9 @@ function noop () {}
520
575
  function isPromise (p) {
521
576
  return !!p && typeof p.then === 'function'
522
577
  }
578
+
579
+ function hypercoreCapability (initiator, handshakeHash, key, cap = b4a.alloc(32)) {
580
+ const ns = initiator ? NS_HYPERCORE_INITIATOR : NS_HYPERCORE_RESPONDER
581
+ sodium.crypto_generichash_batch(cap, [handshakeHash, key], ns)
582
+ return cap
583
+ }