• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

handshake-org / hsd / 12515507044

27 Dec 2024 11:26AM UTC coverage: 71.242% (-0.02%) from 71.265%
12515507044

push

github

nodech
Merge PR #914 from 'nodech/add-mid-checkpoint'

8053 of 13154 branches covered (61.22%)

Branch coverage included in aggregate %.

25712 of 34241 relevant lines covered (75.09%)

34514.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

56.09
/lib/net/brontide.js
1
/*!
2
 * brontide.js - peer-to-peer communication encryption.
3
 * Copyright (c) 2018, Christopher Jeffrey (MIT License).
4
 *
5
 * Resources:
6
 *   https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md
7
 *
8
 * Parts of this software are based on LND:
9
 *   Copyright (C) 2015-2017 The Lightning Network Developers
10
 *   https://github.com/lightningnetwork/lnd/blob/master/brontide/noise.go
11
 *   https://github.com/lightningnetwork/lnd/blob/master/brontide/noise_test.go
12
 *
13
 * We modify the LN handshake in the following way:
14
 *
15
 *   Definition: "elligator" -- an invertible algebraic
16
 *   function which maps a field element to a point. We use
17
 *   the _generalized_ definition here. Think tissue, not
18
 *   kleenex.
19
 *
20
 *   For better indistinguishability and censorship mitigation,
21
 *   we use an Elligator Squared construction as described by
22
 *   Tibouchi[1][2]. As Tibouchi notes, elligators are _more_
23
 *   useful on prime order curves. Any curve with torsion
24
 *   groups must necessarily be distinguishable, as public
25
 *   keys reside in the primary subgroup (though, this may
26
 *   not be true of the Ristretto encoding). An adversary
27
 *   can simply run the forward map and check whether the
28
 *   resulting point is in the primary subgroup.
29
 *
30
 *   We're in a unique position here, since we're already
31
 *   hell bent on using secp256k1 for compat with bitcoin,
32
 *   as opposed to something like ed25519. The fact that
33
 *   secp256k1 has a cofactor of 1 allows us to use a truly
34
 *   indistinguishable encoding.
35
 *
36
 *   The Elligator Squared construction is such that the
37
 *   sender must provide the receiver with two preimages
38
 *   (u, v) which map to a point via `f^2(u,v) -> f(u) + f(v)`
39
 *   where `f(u)` is the forward map. The inverse map f^2^-1(p)
40
 *   is more complex and involves a loop as well as random
41
 *   group element generation.
42
 *
43
 *   Performance may be a concern. Encoding a point requires
44
 *   16 inversions and 20 square roots on average (an average
45
 *   of 4 iterations of the elligator squared loop). This
46
 *   should be on the order of ~3-4 point multiplications.
47
 *   In the future, the inversions can likely be optimized
48
 *   and reduced if multiple iterations are attempted at
49
 *   once.
50
 *
51
 *   The forward map is cheaper, requiring only 6 square
52
 *   roots and 3 inversions. We can visualize this as decoding
53
 *   _nine_ compressed public keys instead of one.
54
 *
55
 *   We can potentially double the performance by moving to
56
 *   a curve which is 3-isogenous to secp256k1[3][4] (idea
57
 *   from [5]). This allows us to use the Simplified
58
 *   Shallue-Woestijne-Ulas map instead of the Shallue-van
59
 *   de Woestijne map, though, the sender may have to modify
60
 *   their private key before executing the actual ECDH (as
61
 *   their public key would be multiplied by 3 by the receiver).
62
 *
63
 *   All bets are off if the user is subjected to a more
64
 *   interactive attack, such as a honeypot/sybil attack,
65
 *   or a MITM. An attack like that will no doubt prove
66
 *   that this particular exchange of messages is that
67
 *   of an end-to-end encryption handshake. However, the
68
 *   construction is here to provide a mitigation against
69
 *   more passive attacks, like non-interactive packet
70
 *   inspection. A user in this situation has plausible
71
 *   deniability with regards to using cryptography over
72
 *   the wire.
73
 *
74
 *   We have an elligator squared implentation in both C and
75
 *   javascript[6][7].
76
 *
77
 *   [1] https://eprint.iacr.org/2014/043.pdf
78
 *   [2] https://www.di.ens.fr/~fouque/pub/latincrypt12.pdf
79
 *   [3] https://gist.github.com/chjj/09c447047d5d0b63afcbbbc484d2c882
80
 *   [4] https://github.com/bcoin-org/bcrypto/commit/aac4464
81
 *   [5] https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/issues/158
82
 *   [6] https://github.com/bcoin-org/bcrypto/blob/master/src/extra256k1/elligator.h
83
 *   [7] https://github.com/bcoin-org/bcrypto/blob/master/lib/js/elliptic.js
84
 */
85

86
'use strict';
87

88
const assert = require('bsert');
1✔
89
const EventEmitter = require('events');
1✔
90
const sha256 = require('bcrypto/lib/sha256');
1✔
91
const aead = require('bcrypto/lib/aead');
1✔
92
const hkdf = require('bcrypto/lib/hkdf');
1✔
93
const secp256k1 = require('bcrypto/lib/secp256k1');
1✔
94
const common = require('./common');
1✔
95

96
/*
97
 * Constants
98
 */
99

100
const ZERO_KEY = Buffer.alloc(32, 0x00);
1✔
101
const ZERO_PUB = Buffer.alloc(33, 0x00);
1✔
102
const EMPTY = Buffer.alloc(0);
1✔
103

104
const PROTOCOL_NAME = 'Noise_XK_secp256k1_ChaChaPoly_SHA256+SVDW_Squared';
1✔
105
const PROLOGUE = 'hns';
1✔
106
const ROTATION_INTERVAL = 1000;
1✔
107
const HEADER_SIZE = 20;
1✔
108
const ACT_ONE_SIZE = 80; // 64 + 16
1✔
109
const ACT_TWO_SIZE = 80; // 64 + 16
1✔
110
const ACT_THREE_SIZE = 65; // 33 + 16 + 16
1✔
111
const MAX_MESSAGE = common.MAX_MESSAGE + 9;
1✔
112

113
const ACT_NONE = 0;
1✔
114
const ACT_ONE = 1;
1✔
115
const ACT_TWO = 2;
1✔
116
const ACT_THREE = 3;
1✔
117
const ACT_DONE = 4;
1✔
118

119
/**
120
 * CipherState
121
 * @extends {EventEmitter}
122
 */
123

124
class CipherState extends EventEmitter {
125
  constructor() {
126
    super();
374✔
127
    this.nonce = 0;
374✔
128
    this.iv = Buffer.alloc(12, 0x00);
374✔
129
    this.key = ZERO_KEY; // secret key
374✔
130
    this.salt = ZERO_KEY;
374✔
131
  }
132

133
  update() {
134
    this.iv.writeUInt32LE(this.nonce, 4, true);
6,055✔
135
    return this.iv;
6,055✔
136
  }
137

138
  initKey(key) {
139
    assert(Buffer.isBuffer(key));
28✔
140
    this.key = key;
28✔
141
    this.nonce = 0;
28✔
142
    this.update();
28✔
143
    return this;
28✔
144
  }
145

146
  initSalt(key, salt) {
147
    assert(Buffer.isBuffer(salt));
12✔
148
    this.salt = salt;
12✔
149
    this.initKey(key);
12✔
150
    return this;
12✔
151
  }
152

153
  rotateKey() {
154
    const info = EMPTY;
8✔
155
    const old = this.key;
8✔
156
    const [salt, next] = expand(old, this.salt, info);
8✔
157

158
    this.salt = salt;
8✔
159
    this.initKey(next);
8✔
160

161
    return this;
8✔
162
  }
163

164
  encrypt(pt, ad) {
165
    const tag = aead.encrypt(this.key, this.iv, pt, ad);
3,016✔
166

167
    this.nonce += 1;
3,016✔
168
    this.update();
3,016✔
169

170
    if (this.nonce === ROTATION_INTERVAL)
3,016✔
171
      this.rotateKey();
4✔
172

173
    return tag;
3,016✔
174
  }
175

176
  decrypt(ct, tag, ad) {
177
    if (!aead.decrypt(this.key, this.iv, ct, tag, ad))
3,011!
178
      return false;
×
179

180
    this.nonce += 1;
3,011✔
181
    this.update();
3,011✔
182

183
    if (this.nonce === ROTATION_INTERVAL)
3,011✔
184
      this.rotateKey();
3✔
185

186
    return true;
3,011✔
187
  }
188
}
189

190
/**
191
 * SymmetricState
192
 * @extends {CipherState}
193
 */
194

195
class SymmetricState extends CipherState {
196
  constructor() {
197
    super();
122✔
198
    this.chain = ZERO_KEY; // chaining key
122✔
199
    this.temp = ZERO_KEY; // temp key
122✔
200
    this.digest = ZERO_KEY; // handshake digest
122✔
201
  }
202

203
  initSymmetric(protocolName) {
204
    assert(typeof protocolName === 'string');
2✔
205

206
    const empty = ZERO_KEY;
2✔
207
    const proto = Buffer.from(protocolName, 'ascii');
2✔
208

209
    this.digest = sha256.digest(proto);
2✔
210
    this.chain = this.digest;
2✔
211
    this.initKey(empty);
2✔
212

213
    return this;
2✔
214
  }
215

216
  mixKey(input) {
217
    const info = EMPTY;
6✔
218
    const secret = input;
6✔
219
    const salt = this.chain;
6✔
220

221
    [this.chain, this.temp] = expand(secret, salt, info);
6✔
222

223
    this.initKey(this.temp);
6✔
224

225
    return this;
6✔
226
  }
227

228
  mixDigest(data, tag) {
229
    return sha256.multi(this.digest, data, tag);
16✔
230
  }
231

232
  mixHash(data, tag) {
233
    this.digest = this.mixDigest(data, tag);
12✔
234
    return this;
12✔
235
  }
236

237
  encryptHash(pt) {
238
    const tag = this.encrypt(pt, this.digest);
4✔
239
    this.mixHash(pt, tag);
4✔
240
    return tag;
4✔
241
  }
242

243
  decryptHash(ct, tag) {
244
    assert(Buffer.isBuffer(tag));
4✔
245

246
    const digest = this.mixDigest(ct, tag);
4✔
247

248
    if (!this.decrypt(ct, tag, this.digest))
4!
249
      return false;
×
250

251
    this.digest = digest;
4✔
252

253
    return true;
4✔
254
  }
255
}
256

257
/**
258
 * HandshakeState
259
 * @extends {SymmetricState}
260
 */
261

262
class HandshakeState extends SymmetricState {
263
  constructor() {
264
    super();
122✔
265
    this.initiator = false;
122✔
266
    this.localStatic = ZERO_KEY;
122✔
267
    this.localEphemeral = ZERO_KEY;
122✔
268
    this.remoteStatic = ZERO_PUB;
122✔
269
    this.remoteEphemeral = ZERO_PUB;
122✔
270
    this.generateKey = () => secp256k1.privateKeyGenerate();
122✔
271
  }
272

273
  initState(initiator, prologue, localPub, remotePub) {
274
    assert(typeof initiator === 'boolean');
2✔
275
    assert(typeof prologue === 'string');
2✔
276
    assert(Buffer.isBuffer(localPub));
2✔
277
    assert(!remotePub || Buffer.isBuffer(remotePub));
2✔
278

279
    this.initiator = initiator;
2✔
280
    this.localStatic = localPub; // private
2✔
281
    this.remoteStatic = remotePub || ZERO_PUB;
2✔
282

283
    this.initSymmetric(PROTOCOL_NAME);
2✔
284
    this.mixHash(Buffer.from(prologue, 'ascii'));
2✔
285

286
    if (initiator) {
2✔
287
      this.mixHash(remotePub);
1✔
288
    } else {
289
      const pub = getPublic(localPub);
1✔
290
      this.mixHash(pub);
1✔
291
    }
292

293
    return this;
2✔
294
  }
295
}
296

297
/**
298
 * Brontide
299
 * @extends {HandshakeState}
300
 */
301

302
class Brontide extends HandshakeState {
303
  constructor() {
304
    super();
122✔
305
    this.sendCipher = new CipherState();
122✔
306
    this.recvCipher = new CipherState();
122✔
307
  }
308

309
  init(initiator, localPub, remotePub) {
310
    return this.initState(initiator, PROLOGUE, localPub, remotePub);
×
311
  }
312

313
  genActOne() {
314
    // e
315
    this.localEphemeral = this.generateKey();
1✔
316

317
    const ephemeral = getPublic(this.localEphemeral);
1✔
318
    const uniform = secp256k1.publicKeyToHash(ephemeral);
1✔
319

320
    this.mixHash(ephemeral);
1✔
321

322
    // es
323
    const s = ecdh(this.remoteStatic, this.localEphemeral);
1✔
324

325
    this.mixKey(s);
1✔
326

327
    const tag = this.encryptHash(EMPTY);
1✔
328
    const actOne = Buffer.allocUnsafe(ACT_ONE_SIZE);
1✔
329

330
    uniform.copy(actOne, 0);
1✔
331
    tag.copy(actOne, 64);
1✔
332

333
    return actOne;
1✔
334
  }
335

336
  recvActOne(actOne) {
337
    assert(Buffer.isBuffer(actOne));
1✔
338

339
    if (actOne.length !== ACT_ONE_SIZE)
1!
340
      throw new Error('Act one: bad size.');
×
341

342
    const u = actOne.slice(0, 64);
1✔
343
    const p = actOne.slice(64);
1✔
344
    const e = secp256k1.publicKeyFromHash(u);
1✔
345

346
    // e
347
    this.remoteEphemeral = e;
1✔
348
    this.mixHash(this.remoteEphemeral);
1✔
349

350
    // es
351
    const s = ecdh(this.remoteEphemeral, this.localStatic);
1✔
352

353
    this.mixKey(s);
1✔
354

355
    if (!this.decryptHash(EMPTY, p))
1!
356
      throw new Error('Act one: bad tag.');
×
357

358
    return this;
1✔
359
  }
360

361
  genActTwo() {
362
    // e
363
    this.localEphemeral = this.generateKey();
1✔
364

365
    const ephemeral = getPublic(this.localEphemeral);
1✔
366
    const uniform = secp256k1.publicKeyToHash(ephemeral);
1✔
367

368
    this.mixHash(ephemeral);
1✔
369

370
    // ee
371
    const s = ecdh(this.remoteEphemeral, this.localEphemeral);
1✔
372

373
    this.mixKey(s);
1✔
374

375
    const tag = this.encryptHash(EMPTY);
1✔
376
    const actTwo = Buffer.allocUnsafe(ACT_TWO_SIZE);
1✔
377

378
    uniform.copy(actTwo, 0);
1✔
379
    tag.copy(actTwo, 64);
1✔
380

381
    return actTwo;
1✔
382
  }
383

384
  recvActTwo(actTwo) {
385
    assert(Buffer.isBuffer(actTwo));
1✔
386

387
    if (actTwo.length !== ACT_TWO_SIZE)
1!
388
      throw new Error('Act two: bad size.');
×
389

390
    const u = actTwo.slice(0, 64);
1✔
391
    const p = actTwo.slice(64);
1✔
392
    const e = secp256k1.publicKeyFromHash(u);
1✔
393

394
    // e
395
    this.remoteEphemeral = e;
1✔
396
    this.mixHash(this.remoteEphemeral);
1✔
397

398
    // ee
399
    const s = ecdh(this.remoteEphemeral, this.localEphemeral);
1✔
400

401
    this.mixKey(s);
1✔
402

403
    if (!this.decryptHash(EMPTY, p))
1!
404
      throw new Error('Act two: bad tag.');
×
405

406
    return this;
1✔
407
  }
408

409
  genActThree() {
410
    const ourPubkey = getPublic(this.localStatic);
1✔
411
    const tag1 = this.encryptHash(ourPubkey);
1✔
412
    const ct = ourPubkey;
1✔
413

414
    const s = ecdh(this.remoteEphemeral, this.localStatic);
1✔
415

416
    this.mixKey(s);
1✔
417

418
    const tag2 = this.encryptHash(EMPTY);
1✔
419
    const actThree = Buffer.allocUnsafe(ACT_THREE_SIZE);
1✔
420

421
    ct.copy(actThree, 0);
1✔
422
    tag1.copy(actThree, 33);
1✔
423
    tag2.copy(actThree, 49);
1✔
424

425
    this.split();
1✔
426

427
    return actThree;
1✔
428
  }
429

430
  recvActThree(actThree) {
431
    assert(Buffer.isBuffer(actThree));
1✔
432

433
    if (actThree.length !== ACT_THREE_SIZE)
1!
434
      throw new Error('Act three: bad size.');
×
435

436
    const s1 = actThree.slice(0, 33);
1✔
437
    const p1 = actThree.slice(33, 49);
1✔
438

439
    const s2 = actThree.slice(49, 49);
1✔
440
    const p2 = actThree.slice(49, 65);
1✔
441

442
    // s
443
    if (!this.decryptHash(s1, p1))
1!
444
      throw new Error('Act three: bad tag.');
×
445

446
    const remotePub = s1;
1✔
447

448
    this.remoteStatic = remotePub;
1✔
449

450
    // se
451
    const se = ecdh(this.remoteStatic, this.localEphemeral);
1✔
452

453
    this.mixKey(se);
1✔
454

455
    if (!this.decryptHash(s2, p2))
1!
456
      throw new Error('Act three: bad tag.');
×
457

458
    this.split();
1✔
459

460
    return this;
1✔
461
  }
462

463
  split() {
464
    const [h1, h2] = expand(EMPTY, this.chain, EMPTY);
2✔
465

466
    if (this.initiator) {
2✔
467
      const sendKey = h1;
1✔
468
      this.sendCipher.initSalt(sendKey, this.chain);
1✔
469
      const recvKey = h2;
1✔
470
      this.recvCipher.initSalt(recvKey, this.chain);
1✔
471
    } else {
472
      const recvKey = h1;
1✔
473
      this.recvCipher.initSalt(recvKey, this.chain);
1✔
474
      const sendKey = h2;
1✔
475
      this.sendCipher.initSalt(sendKey, this.chain);
1✔
476
    }
477

478
    return this;
2✔
479
  }
480

481
  write(data) {
482
    assert(Buffer.isBuffer(data));
1,002✔
483
    assert(data.length <= 0xffff);
1,002✔
484

485
    const packet = Buffer.allocUnsafe(2 + 16 + data.length + 16);
1,002✔
486
    packet.writeUInt16BE(data.length, 0);
1,002✔
487
    data.copy(packet, 2 + 16);
1,002✔
488

489
    const len = packet.slice(0, 2);
1,002✔
490
    const ta1 = packet.slice(2, 18);
1,002✔
491
    const msg = packet.slice(18, 18 + data.length);
1,002✔
492
    const ta2 = packet.slice(18 + data.length, 18 + data.length + 16);
1,002✔
493

494
    const tag1 = this.sendCipher.encrypt(len);
1,002✔
495
    tag1.copy(ta1, 0);
1,002✔
496

497
    const tag2 = this.sendCipher.encrypt(msg);
1,002✔
498
    tag2.copy(ta2, 0);
1,002✔
499

500
    return packet;
1,002✔
501
  }
502

503
  read(packet) {
504
    assert(Buffer.isBuffer(packet));
1,002✔
505

506
    const len = packet.slice(0, 2);
1,002✔
507
    const ta1 = packet.slice(2, 18);
1,002✔
508

509
    if (!this.recvCipher.decrypt(len, ta1))
1,002!
510
      throw new Error('Bad tag for header.');
×
511

512
    const size = len.readUInt16BE(0, true);
1,002✔
513
    assert(packet.length === 18 + size + 16);
1,002✔
514

515
    const msg = packet.slice(18, 18 + size);
1,002✔
516
    const ta2 = packet.slice(18 + size, 18 + size + 16);
1,002✔
517

518
    if (!this.recvCipher.decrypt(msg, ta2))
1,002!
519
      throw new Error('Bad tag for message.');
×
520

521
    return msg;
1,002✔
522
  }
523
}
524

525
/**
526
 * BrontideStream
527
 * @extends {Brontide}
528
 */
529

530
class BrontideStream extends Brontide {
531
  constructor() {
532
    super();
120✔
533
    this.socket = null;
120✔
534
    this.state = ACT_NONE;
120✔
535
    this.pending = [];
120✔
536
    this.total = 0;
120✔
537
    this.waiting = 0;
120✔
538
    this.hasSize = false;
120✔
539
    this.buffer = [];
120✔
540
    this.onData = data => this.feed(data);
120✔
541
    this.onConnect = () => this.start();
120✔
542
  }
543

544
  accept(socket, ourKey) {
545
    assert(!this.socket);
×
546
    assert(socket);
×
547
    this.socket = socket;
×
548
    this.init(false, ourKey);
×
549
    this.socket.on('data', this.onData);
×
550
    this.start();
×
551
    return this;
×
552
  }
553

554
  connect(socket, ourKey, theirKey) {
555
    assert(!this.socket);
×
556
    assert(socket);
×
557
    this.socket = socket;
×
558
    this.init(true, ourKey, theirKey);
×
559
    this.socket.on('connect', this.onConnect);
×
560
    this.socket.on('data', this.onData);
×
561
    return this;
×
562
  }
563

564
  start() {
565
    if (this.initiator) {
×
566
      this.state = ACT_TWO;
×
567
      this.waiting = ACT_TWO_SIZE;
×
568
      try {
×
569
        this.socket.write(this.genActOne());
×
570
      } catch (e) {
571
        setImmediate(() => {
×
572
          this.destroy();
×
573
          this.emit('error', e);
×
574
        });
575
        return this;
×
576
      }
577
    } else {
578
      this.state = ACT_ONE;
×
579
      this.waiting = ACT_ONE_SIZE;
×
580
    }
581
    return this;
×
582
  }
583

584
  unleash() {
585
    assert(this.state === ACT_DONE);
×
586

587
    for (const buf of this.buffer)
×
588
      this.write(buf);
×
589

590
    this.buffer.length = 0;
×
591

592
    return this;
×
593
  }
594

595
  destroy() {
596
    this.state = ACT_NONE;
×
597
    this.pending.length = 0;
×
598
    this.total = 0;
×
599
    this.waiting = 0;
×
600
    this.hasSize = false;
×
601
    this.buffer.length = 0;
×
602
    this.socket.removeListener('connect', this.onConnect);
×
603
    this.socket.removeListener('data', this.onData);
×
604
    return this;
×
605
  }
606

607
  write(data) {
608
    assert(Buffer.isBuffer(data));
×
609

610
    if (this.state === ACT_NONE)
×
611
      return false;
×
612

613
    if (this.state !== ACT_DONE) {
×
614
      this.buffer.push(data);
×
615
      return false;
×
616
    }
617

618
    assert(data.length <= 0xffffffff);
×
619

620
    const len = Buffer.allocUnsafe(4);
×
621
    len.writeUInt32LE(data.length, 0);
×
622

623
    let r = 1;
×
624

625
    const tag1 = this.sendCipher.encrypt(len);
×
626

627
    r &= this.socket.write(len);
×
628
    r &= this.socket.write(tag1);
×
629

630
    const tag2 = this.sendCipher.encrypt(data);
×
631

632
    r &= this.socket.write(data);
×
633
    r &= this.socket.write(tag2);
×
634

635
    return Boolean(r);
×
636
  }
637

638
  feed(data) {
639
    assert(Buffer.isBuffer(data));
×
640

641
    if (this.state === ACT_NONE)
×
642
      return;
×
643

644
    this.total += data.length;
×
645
    this.pending.push(data);
×
646

647
    while (this.total >= this.waiting) {
×
648
      const chunk = this.read(this.waiting);
×
649
      if (!this.parse(chunk))
×
650
        break;
×
651
    }
652
  }
653

654
  read(size) {
655
    assert((size >>> 0) === size);
×
656
    assert(this.total >= size, 'Reading too much.');
×
657

658
    if (size === 0)
×
659
      return Buffer.alloc(0);
×
660

661
    const pending = this.pending[0];
×
662

663
    if (pending.length > size) {
×
664
      const chunk = pending.slice(0, size);
×
665
      this.pending[0] = pending.slice(size);
×
666
      this.total -= chunk.length;
×
667
      return chunk;
×
668
    }
669

670
    if (pending.length === size) {
×
671
      const chunk = this.pending.shift();
×
672
      this.total -= chunk.length;
×
673
      return chunk;
×
674
    }
675

676
    const chunk = Buffer.allocUnsafe(size);
×
677

678
    let off = 0;
×
679

680
    while (off < chunk.length) {
×
681
      const pending = this.pending[0];
×
682
      const len = pending.copy(chunk, off);
×
683
      if (len === pending.length)
×
684
        this.pending.shift();
×
685
      else
686
        this.pending[0] = pending.slice(len);
×
687
      off += len;
×
688
    }
689

690
    assert.strictEqual(off, chunk.length);
×
691

692
    this.total -= chunk.length;
×
693

694
    return chunk;
×
695
  }
696

697
  parse(data) {
698
    assert(Buffer.isBuffer(data));
×
699

700
    try {
×
701
      this._parse(data);
×
702
      return true;
×
703
    } catch (e) {
704
      this.destroy();
×
705
      this.emit('error', e);
×
706
      return false;
×
707
    }
708
  }
709

710
  _parse(data) {
711
    if (this.initiator) {
×
712
      switch (this.state) {
×
713
        case ACT_TWO:
714
          this.recvActTwo(data);
×
715
          this.socket.write(this.genActThree());
×
716
          this.state = ACT_DONE;
×
717
          this.waiting = HEADER_SIZE;
×
718
          this.unleash();
×
719
          this.emit('connect');
×
720
          return;
×
721
        default:
722
          assert(this.state === ACT_DONE);
×
723
          break;
×
724
      }
725
    } else {
726
      switch (this.state) {
×
727
        case ACT_ONE:
728
          this.recvActOne(data);
×
729
          this.socket.write(this.genActTwo());
×
730
          this.state = ACT_THREE;
×
731
          this.waiting = ACT_THREE_SIZE;
×
732
          return;
×
733
        case ACT_THREE:
734
          this.recvActThree(data);
×
735
          this.state = ACT_DONE;
×
736
          this.waiting = HEADER_SIZE;
×
737
          this.unleash();
×
738
          this.emit('connect');
×
739
          return;
×
740
        default:
741
          assert(this.state === ACT_DONE);
×
742
          break;
×
743
      }
744
    }
745

746
    if (!this.hasSize) {
×
747
      assert(this.waiting === HEADER_SIZE);
×
748
      assert(data.length === HEADER_SIZE);
×
749

750
      const len = data.slice(0, 4);
×
751
      const tag = data.slice(4, 20);
×
752

753
      if (!this.recvCipher.decrypt(len, tag))
×
754
        throw new Error('Bad tag for header.');
×
755

756
      const size = len.readUInt32LE(0, true);
×
757

758
      if (size > MAX_MESSAGE)
×
759
        throw new Error('Bad packet size.');
×
760

761
      this.hasSize = true;
×
762
      this.waiting = size + 16;
×
763

764
      return;
×
765
    }
766

767
    const payload = data.slice(0, this.waiting - 16);
×
768
    const tag = data.slice(this.waiting - 16, this.waiting);
×
769

770
    this.hasSize = false;
×
771
    this.waiting = HEADER_SIZE;
×
772

773
    if (!this.recvCipher.decrypt(payload, tag))
×
774
      throw new Error('Bad tag for message.');
×
775

776
    this.emit('data', payload);
×
777
  }
778

779
  static fromInbound(socket, ourKey) {
780
    return new BrontideStream().accept(socket, ourKey);
×
781
  }
782

783
  static fromOutbound(socket, ourKey, theirKey) {
784
    return new BrontideStream().connect(socket, ourKey, theirKey);
×
785
  }
786
}
787

788
/*
789
 * Helpers
790
 */
791

792
function ecdh(publicKey, privateKey) {
793
  const secret = secp256k1.derive(publicKey, privateKey, true);
6✔
794
  return sha256.digest(secret);
6✔
795
}
796

797
function getPublic(priv) {
798
  return secp256k1.publicKeyCreate(priv, true);
4✔
799
}
800

801
function expand(secret, salt, info) {
802
  const prk = hkdf.extract(sha256, secret, salt);
16✔
803
  const out = hkdf.expand(sha256, prk, info, 64);
16✔
804
  return [out.slice(0, 32), out.slice(32, 64)];
16✔
805
}
806

807
/*
808
 * Expose
809
 */
810

811
exports.CipherState = CipherState;
1✔
812
exports.SymmetricState = SymmetricState;
1✔
813
exports.HandshakeState = HandshakeState;
1✔
814
exports.Brontide = Brontide;
1✔
815
exports.BrontideStream = BrontideStream;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc