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

lightningnetwork / lnd / 17991712134

24 Sep 2025 10:49PM UTC coverage: 57.143% (-9.5%) from 66.653%
17991712134

push

github

web-flow
Merge pull request #10183 from Roasbeef/brontide-static-alloac

brontide: eliminate all allocations from WriteMessage+Flush

59 of 67 new or added lines in 3 files covered. (88.06%)

28705 existing lines in 459 files now uncovered.

99466 of 174065 relevant lines covered (57.14%)

1.77 hits per line

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

87.07
/brontide/noise.go
1
package brontide
2

3
import (
4
        "crypto/cipher"
5
        "crypto/sha256"
6
        "encoding/binary"
7
        "errors"
8
        "fmt"
9
        "io"
10
        "math"
11
        "sync"
12
        "time"
13

14
        "github.com/btcsuite/btcd/btcec/v2"
15
        "github.com/lightningnetwork/lnd/keychain"
16
        "golang.org/x/crypto/chacha20poly1305"
17
        "golang.org/x/crypto/hkdf"
18
)
19

20
const (
21
        // protocolName is the precise instantiation of the Noise protocol
22
        // handshake at the center of Brontide. This value will be used as part
23
        // of the prologue. If the initiator and responder aren't using the
24
        // exact same string for this value, along with prologue of the Bitcoin
25
        // network, then the initial handshake will fail.
26
        protocolName = "Noise_XK_secp256k1_ChaChaPoly_SHA256"
27

28
        // macSize is the length in bytes of the tags generated by poly1305.
29
        macSize = 16
30

31
        // lengthHeaderSize is the number of bytes used to prefix encode the
32
        // length of a message payload.
33
        lengthHeaderSize = 2
34

35
        // encHeaderSize is the number of bytes required to hold an encrypted
36
        // header and it's MAC.
37
        encHeaderSize = lengthHeaderSize + macSize
38

39
        // maxMessageSize is the maximum size of an encrypted message including
40
        // the MAC. This is the max payload (65535) plus the MAC size (16).
41
        maxMessageSize = math.MaxUint16 + macSize
42

43
        // keyRotationInterval is the number of messages sent on a single
44
        // cipher stream before the keys are rotated forwards.
45
        keyRotationInterval = 1000
46

47
        // handshakeReadTimeout is a read timeout that will be enforced when
48
        // waiting for data payloads during the various acts of Brontide. If
49
        // the remote party fails to deliver the proper payload within this
50
        // time frame, then we'll fail the connection.
51
        handshakeReadTimeout = time.Second * 5
52
)
53

54
var (
55
        // ErrMaxMessageLengthExceeded is returned when a message to be written to
56
        // the cipher session exceeds the maximum allowed message payload.
57
        ErrMaxMessageLengthExceeded = errors.New("the generated payload exceeds " +
58
                "the max allowed message length of (2^16)-1")
59

60
        // ErrMessageNotFlushed signals that the connection cannot accept a new
61
        // message because the prior message has not been fully flushed.
62
        ErrMessageNotFlushed = errors.New("prior message not flushed")
63

64
        // lightningPrologue is the noise prologue that is used to initialize
65
        // the brontide noise handshake.
66
        lightningPrologue = []byte("lightning")
67

68
        // ephemeralGen is the default ephemeral key generator, used to derive a
69
        // unique ephemeral key for each brontide handshake.
70
        ephemeralGen = func() (*btcec.PrivateKey, error) {
3✔
71
                return btcec.NewPrivateKey()
3✔
72
        }
3✔
73

74
        // headerBufferPool is a pool for encrypted header buffers.
75
        headerBufferPool = &sync.Pool{
76
                New: func() interface{} {
3✔
77
                        b := make([]byte, 0, encHeaderSize)
3✔
78
                        return &b
3✔
79
                },
3✔
80
        }
81

82
        // bodyBufferPool is a pool for encrypted message body buffers.
83
        bodyBufferPool = &sync.Pool{
84
                New: func() interface{} {
3✔
85
                        // Allocate max size to avoid reallocation.
3✔
86
                        // maxMessageSize already includes the MAC.
3✔
87
                        b := make([]byte, 0, maxMessageSize)
3✔
88
                        return &b
3✔
89
                },
3✔
90
        }
91
)
92

93
// ecdh performs an ECDH operation between pub and priv. The returned value is
94
// the sha256 of the compressed shared point.
95
func ecdh(pub *btcec.PublicKey, priv keychain.SingleKeyECDH) ([]byte, error) {
3✔
96
        hash, err := priv.ECDH(pub)
3✔
97
        return hash[:], err
3✔
98
}
3✔
99

100
// cipherState encapsulates the state for the AEAD which will be used to
101
// encrypt+authenticate any payloads sent during the handshake, and messages
102
// sent once the handshake has completed.
103
type cipherState struct {
104
        // nonce is the nonce passed into the chacha20-poly1305 instance for
105
        // encryption+decryption. The nonce is incremented after each successful
106
        // encryption/decryption.
107
        //
108
        // TODO(roasbeef): this should actually be 96 bit
109
        nonce uint64
110

111
        // nonceBuffer is a reusable buffer for the nonce to avoid allocations.
112
        nonceBuffer [12]byte
113

114
        // secretKey is the shared symmetric key which will be used to
115
        // instantiate the cipher.
116
        //
117
        // TODO(roasbeef): m-lock??
118
        secretKey [32]byte
119

120
        // salt is an additional secret which is used during key rotation to
121
        // generate new keys.
122
        salt [32]byte
123

124
        // cipher is an instance of the ChaCha20-Poly1305 AEAD construction
125
        // created using the secretKey above.
126
        cipher cipher.AEAD
127
}
128

129
// Encrypt returns a ciphertext which is the encryption of the plainText
130
// observing the passed associatedData within the AEAD construction.
131
func (c *cipherState) Encrypt(associatedData, cipherText, plainText []byte) []byte {
3✔
132
        defer func() {
6✔
133
                c.nonce++
3✔
134

3✔
135
                if c.nonce == keyRotationInterval {
6✔
136
                        c.rotateKey()
3✔
137
                }
3✔
138
        }()
139

140
        // Write the nonce counter to the buffer (bytes 4-11).
141
        binary.LittleEndian.PutUint64(c.nonceBuffer[4:], c.nonce)
3✔
142

3✔
143
        return c.cipher.Seal(
3✔
144
                cipherText, c.nonceBuffer[:], plainText, associatedData,
3✔
145
        )
3✔
146
}
147

148
// Decrypt attempts to decrypt the passed ciphertext observing the specified
149
// associatedData within the AEAD construction. In the case that the final MAC
150
// check fails, then a non-nil error will be returned.
151
func (c *cipherState) Decrypt(associatedData, plainText, cipherText []byte) ([]byte, error) {
3✔
152
        defer func() {
6✔
153
                c.nonce++
3✔
154

3✔
155
                if c.nonce == keyRotationInterval {
6✔
156
                        c.rotateKey()
3✔
157
                }
3✔
158
        }()
159

160
        // Write the nonce counter to the buffer (bytes 4-11).
161
        binary.LittleEndian.PutUint64(c.nonceBuffer[4:], c.nonce)
3✔
162

3✔
163
        return c.cipher.Open(
3✔
164
                plainText, c.nonceBuffer[:], cipherText, associatedData,
3✔
165
        )
3✔
166
}
167

168
// InitializeKey initializes the secret key and AEAD cipher scheme based off of
169
// the passed key.
170
func (c *cipherState) InitializeKey(key [32]byte) {
3✔
171
        c.secretKey = key
3✔
172
        c.nonce = 0
3✔
173

3✔
174
        // Safe to ignore the error here as our key is properly sized
3✔
175
        // (32-bytes).
3✔
176
        c.cipher, _ = chacha20poly1305.New(c.secretKey[:])
3✔
177
}
3✔
178

179
// InitializeKeyWithSalt is identical to InitializeKey however it also sets the
180
// cipherState's salt field which is used for key rotation.
181
func (c *cipherState) InitializeKeyWithSalt(salt, key [32]byte) {
3✔
182
        c.salt = salt
3✔
183
        c.InitializeKey(key)
3✔
184
}
3✔
185

186
// rotateKey rotates the current encryption/decryption key for this cipherState
187
// instance. Key rotation is performed by ratcheting the current key forward
188
// using an HKDF invocation with the cipherState's salt as the salt, and the
189
// current key as the input.
190
func (c *cipherState) rotateKey() {
3✔
191
        var (
3✔
192
                info    []byte
3✔
193
                nextKey [32]byte
3✔
194
        )
3✔
195

3✔
196
        oldKey := c.secretKey
3✔
197
        h := hkdf.New(sha256.New, oldKey[:], c.salt[:], info)
3✔
198

3✔
199
        // hkdf(ck, k, zero)
3✔
200
        // |
3✔
201
        // | \
3✔
202
        // |  \
3✔
203
        // ck  k'
3✔
204
        h.Read(c.salt[:])
3✔
205
        h.Read(nextKey[:])
3✔
206

3✔
207
        c.InitializeKey(nextKey)
3✔
208
}
3✔
209

210
// symmetricState encapsulates a cipherState object and houses the ephemeral
211
// handshake digest state. This struct is used during the handshake to derive
212
// new shared secrets based off of the result of ECDH operations. Ultimately,
213
// the final key yielded by this struct is the result of an incremental
214
// Triple-DH operation.
215
type symmetricState struct {
216
        cipherState
217

218
        // chainingKey is used as the salt to the HKDF function to derive a new
219
        // chaining key as well as a new tempKey which is used for
220
        // encryption/decryption.
221
        chainingKey [32]byte
222

223
        // tempKey is the latter 32 bytes resulted from the latest HKDF
224
        // iteration. This key is used to encrypt/decrypt any handshake
225
        // messages or payloads sent until the next DH operation is executed.
226
        tempKey [32]byte
227

228
        // handshakeDigest is the cumulative hash digest of all handshake
229
        // messages sent from start to finish. This value is never transmitted
230
        // to the other side, but will be used as the AD when
231
        // encrypting/decrypting messages using our AEAD construction.
232
        handshakeDigest [32]byte
233
}
234

235
// mixKey implements a basic HKDF-based key ratchet. This method is called
236
// with the result of each DH output generated during the handshake process.
237
// The first 32 bytes extract from the HKDF reader is the next chaining key,
238
// then latter 32 bytes become the temp secret key using within any future AEAD
239
// operations until another DH operation is performed.
240
func (s *symmetricState) mixKey(input []byte) {
3✔
241
        var info []byte
3✔
242

3✔
243
        secret := input
3✔
244
        salt := s.chainingKey
3✔
245
        h := hkdf.New(sha256.New, secret, salt[:], info)
3✔
246

3✔
247
        // hkdf(ck, input, zero)
3✔
248
        // |
3✔
249
        // | \
3✔
250
        // |  \
3✔
251
        // ck  k
3✔
252
        h.Read(s.chainingKey[:])
3✔
253
        h.Read(s.tempKey[:])
3✔
254

3✔
255
        // cipher.k = temp_key
3✔
256
        s.InitializeKey(s.tempKey)
3✔
257
}
3✔
258

259
// mixHash hashes the passed input data into the cumulative handshake digest.
260
// The running result of this value (h) is used as the associated data in all
261
// decryption/encryption operations.
262
func (s *symmetricState) mixHash(data []byte) {
3✔
263
        h := sha256.New()
3✔
264
        h.Write(s.handshakeDigest[:])
3✔
265
        h.Write(data)
3✔
266

3✔
267
        copy(s.handshakeDigest[:], h.Sum(nil))
3✔
268
}
3✔
269

270
// EncryptAndHash returns the authenticated encryption of the passed plaintext.
271
// When encrypting the handshake digest (h) is used as the associated data to
272
// the AEAD cipher.
273
func (s *symmetricState) EncryptAndHash(plaintext []byte) []byte {
3✔
274
        ciphertext := s.Encrypt(s.handshakeDigest[:], nil, plaintext)
3✔
275

3✔
276
        s.mixHash(ciphertext)
3✔
277

3✔
278
        return ciphertext
3✔
279
}
3✔
280

281
// DecryptAndHash returns the authenticated decryption of the passed
282
// ciphertext. When encrypting the handshake digest (h) is used as the
283
// associated data to the AEAD cipher.
284
func (s *symmetricState) DecryptAndHash(ciphertext []byte) ([]byte, error) {
3✔
285
        plaintext, err := s.Decrypt(s.handshakeDigest[:], nil, ciphertext)
3✔
286
        if err != nil {
3✔
UNCOV
287
                return nil, err
×
UNCOV
288
        }
×
289

290
        s.mixHash(ciphertext)
3✔
291

3✔
292
        return plaintext, nil
3✔
293
}
294

295
// InitializeSymmetric initializes the symmetric state by setting the handshake
296
// digest (h) and the chaining key (ck) to protocol name.
297
func (s *symmetricState) InitializeSymmetric(protocolName []byte) {
3✔
298
        var empty [32]byte
3✔
299

3✔
300
        s.handshakeDigest = sha256.Sum256(protocolName)
3✔
301
        s.chainingKey = s.handshakeDigest
3✔
302
        s.InitializeKey(empty)
3✔
303
}
3✔
304

305
// handshakeState encapsulates the symmetricState and keeps track of all the
306
// public keys (static and ephemeral) for both sides during the handshake
307
// transcript. If the handshake completes successfully, then two instances of a
308
// cipherState are emitted: one to encrypt messages from initiator to
309
// responder, and the other for the opposite direction.
310
type handshakeState struct {
311
        symmetricState
312

313
        initiator bool
314

315
        localStatic    keychain.SingleKeyECDH
316
        localEphemeral keychain.SingleKeyECDH // nolint (false positive)
317

318
        remoteStatic    *btcec.PublicKey
319
        remoteEphemeral *btcec.PublicKey
320
}
321

322
// newHandshakeState returns a new instance of the handshake state initialized
323
// with the prologue and protocol name. If this is the responder's handshake
324
// state, then the remotePub can be nil.
325
func newHandshakeState(initiator bool, prologue []byte,
326
        localKey keychain.SingleKeyECDH,
327
        remotePub *btcec.PublicKey) handshakeState {
3✔
328

3✔
329
        h := handshakeState{
3✔
330
                initiator:    initiator,
3✔
331
                localStatic:  localKey,
3✔
332
                remoteStatic: remotePub,
3✔
333
        }
3✔
334

3✔
335
        // Set the current chaining key and handshake digest to the hash of the
3✔
336
        // protocol name, and additionally mix in the prologue. If either sides
3✔
337
        // disagree about the prologue or protocol name, then the handshake
3✔
338
        // will fail.
3✔
339
        h.InitializeSymmetric([]byte(protocolName))
3✔
340
        h.mixHash(prologue)
3✔
341

3✔
342
        // In Noise_XK, the initiator should know the responder's static
3✔
343
        // public key, therefore we include the responder's static key in the
3✔
344
        // handshake digest. If the initiator gets this value wrong, then the
3✔
345
        // handshake will fail.
3✔
346
        if initiator {
6✔
347
                h.mixHash(remotePub.SerializeCompressed())
3✔
348
        } else {
6✔
349
                h.mixHash(localKey.PubKey().SerializeCompressed())
3✔
350
        }
3✔
351

352
        return h
3✔
353
}
354

355
// EphemeralGenerator is a functional option that allows callers to substitute
356
// a custom function for use when generating ephemeral keys for ActOne or
357
// ActTwo. The function closure returned by this function can be passed into
358
// NewBrontideMachine as a function option parameter.
UNCOV
359
func EphemeralGenerator(gen func() (*btcec.PrivateKey, error)) func(*Machine) {
×
UNCOV
360
        return func(m *Machine) {
×
UNCOV
361
                m.ephemeralGen = gen
×
UNCOV
362
        }
×
363
}
364

365
// Machine is a state-machine which implements Brontide: an
366
// Authenticated-key Exchange in Three Acts. Brontide is derived from the Noise
367
// framework, specifically implementing the Noise_XK handshake. Once the
368
// initial 3-act handshake has completed all messages are encrypted with a
369
// chacha20 AEAD cipher. On the wire, all messages are prefixed with an
370
// authenticated+encrypted length field. Additionally, the encrypted+auth'd
371
// length prefix is used as the AD when encrypting+decryption messages. This
372
// construction provides confidentiality of packet length, avoids introducing
373
// a padding-oracle, and binds the encrypted packet length to the packet
374
// itself.
375
//
376
// The acts proceeds the following order (initiator on the left):
377
//
378
//        GenActOne()   ->
379
//                          RecvActOne()
380
//                      <-  GenActTwo()
381
//        RecvActTwo()
382
//        GenActThree() ->
383
//                          RecvActThree()
384
//
385
// This exchange corresponds to the following Noise handshake:
386
//
387
//        <- s
388
//        ...
389
//        -> e, es
390
//        <- e, ee
391
//        -> s, se
392
type Machine struct {
393
        sendCipher cipherState
394
        recvCipher cipherState
395

396
        ephemeralGen func() (*btcec.PrivateKey, error)
397

398
        handshakeState
399

400
        // nextCipherHeader is a static buffer that we'll use to read in the
401
        // next ciphertext header from the wire. The header is a 2 byte length
402
        // (of the next ciphertext), followed by a 16 byte MAC.
403
        nextCipherHeader [encHeaderSize]byte
404

405
        // pktLenBuffer is a reusable buffer for encoding the packet length.
406
        pktLenBuffer [lengthHeaderSize]byte
407

408
        // nextHeaderSend holds a reference to the remaining header bytes to
409
        // write out for a pending message. This allows us to tolerate timeout
410
        // errors that cause partial writes.
411
        nextHeaderSend []byte
412

413
        // nextBodySend holds a reference to the remaining body bytes to write
414
        // out for a pending message. This allows us to tolerate timeout errors
415
        // that cause partial writes.
416
        nextBodySend []byte
417

418
        // pooledHeaderBuf is the pooled buffer used for the header, which we
419
        // need to track so we can return it to the pool when done.
420
        pooledHeaderBuf *[]byte
421

422
        // pooledBodyBuf is the pooled buffer used for the body, which we need
423
        // to track so we can return it to the pool when done.
424
        pooledBodyBuf *[]byte
425
}
426

427
// NewBrontideMachine creates a new instance of the brontide state-machine. If
428
// the responder (listener) is creating the object, then the remotePub should
429
// be nil. The handshake state within brontide is initialized using the ascii
430
// string "lightning" as the prologue. The last parameter is a set of variadic
431
// arguments for adding additional options to the brontide Machine
432
// initialization.
433
func NewBrontideMachine(initiator bool, localKey keychain.SingleKeyECDH,
434
        remotePub *btcec.PublicKey, options ...func(*Machine)) *Machine {
3✔
435

3✔
436
        handshake := newHandshakeState(
3✔
437
                initiator, lightningPrologue, localKey, remotePub,
3✔
438
        )
3✔
439

3✔
440
        m := &Machine{
3✔
441
                handshakeState: handshake,
3✔
442
                ephemeralGen:   ephemeralGen,
3✔
443
        }
3✔
444

3✔
445
        // With the default options established, we'll now process all the
3✔
446
        // options passed in as parameters.
3✔
447
        for _, option := range options {
3✔
UNCOV
448
                option(m)
×
UNCOV
449
        }
×
450

451
        return m
3✔
452
}
453

454
const (
455
        // HandshakeVersion is the expected version of the brontide handshake.
456
        // Any messages that carry a different version will cause the handshake
457
        // to abort immediately.
458
        HandshakeVersion = byte(0)
459

460
        // ActOneSize is the size of the packet sent from initiator to
461
        // responder in ActOne. The packet consists of a handshake version, an
462
        // ephemeral key in compressed format, and a 16-byte poly1305 tag.
463
        //
464
        // 1 + 33 + 16
465
        ActOneSize = 50
466

467
        // ActTwoSize is the size the packet sent from responder to initiator
468
        // in ActTwo. The packet consists of a handshake version, an ephemeral
469
        // key in compressed format and a 16-byte poly1305 tag.
470
        //
471
        // 1 + 33 + 16
472
        ActTwoSize = 50
473

474
        // ActThreeSize is the size of the packet sent from initiator to
475
        // responder in ActThree. The packet consists of a handshake version,
476
        // the initiators static key encrypted with strong forward secrecy and
477
        // a 16-byte poly1035 tag.
478
        //
479
        // 1 + 33 + 16 + 16
480
        ActThreeSize = 66
481
)
482

483
// GenActOne generates the initial packet (act one) to be sent from initiator
484
// to responder. During act one the initiator generates a fresh ephemeral key,
485
// hashes it into the handshake digest, and performs an ECDH between this key
486
// and the responder's static key. Future payloads are encrypted with a key
487
// derived from this result.
488
//
489
//        -> e, es
490
func (b *Machine) GenActOne() ([ActOneSize]byte, error) {
3✔
491
        var actOne [ActOneSize]byte
3✔
492

3✔
493
        // e
3✔
494
        localEphemeral, err := b.ephemeralGen()
3✔
495
        if err != nil {
3✔
496
                return actOne, err
×
497
        }
×
498
        b.localEphemeral = &keychain.PrivKeyECDH{
3✔
499
                PrivKey: localEphemeral,
3✔
500
        }
3✔
501

3✔
502
        ephemeral := localEphemeral.PubKey().SerializeCompressed()
3✔
503
        b.mixHash(ephemeral)
3✔
504

3✔
505
        // es
3✔
506
        s, err := ecdh(b.remoteStatic, b.localEphemeral)
3✔
507
        if err != nil {
3✔
508
                return actOne, err
×
509
        }
×
510
        b.mixKey(s[:])
3✔
511

3✔
512
        authPayload := b.EncryptAndHash([]byte{})
3✔
513

3✔
514
        actOne[0] = HandshakeVersion
3✔
515
        copy(actOne[1:34], ephemeral)
3✔
516
        copy(actOne[34:], authPayload)
3✔
517

3✔
518
        return actOne, nil
3✔
519
}
520

521
// RecvActOne processes the act one packet sent by the initiator. The responder
522
// executes the mirrored actions to that of the initiator extending the
523
// handshake digest and deriving a new shared secret based on an ECDH with the
524
// initiator's ephemeral key and responder's static key.
525
func (b *Machine) RecvActOne(actOne [ActOneSize]byte) error {
3✔
526
        var (
3✔
527
                err error
3✔
528
                e   [33]byte
3✔
529
                p   [16]byte
3✔
530
        )
3✔
531

3✔
532
        // If the handshake version is unknown, then the handshake fails
3✔
533
        // immediately.
3✔
534
        if actOne[0] != HandshakeVersion {
3✔
UNCOV
535
                return fmt.Errorf("act one: invalid handshake version: %v, "+
×
UNCOV
536
                        "only %v is valid, msg=%x", actOne[0], HandshakeVersion,
×
UNCOV
537
                        actOne[:])
×
UNCOV
538
        }
×
539

540
        copy(e[:], actOne[1:34])
3✔
541
        copy(p[:], actOne[34:])
3✔
542

3✔
543
        // e
3✔
544
        b.remoteEphemeral, err = btcec.ParsePubKey(e[:])
3✔
545
        if err != nil {
3✔
UNCOV
546
                return err
×
UNCOV
547
        }
×
548
        b.mixHash(b.remoteEphemeral.SerializeCompressed())
3✔
549

3✔
550
        // es
3✔
551
        s, err := ecdh(b.remoteEphemeral, b.localStatic)
3✔
552
        if err != nil {
3✔
553
                return err
×
554
        }
×
555
        b.mixKey(s)
3✔
556

3✔
557
        // If the initiator doesn't know our static key, then this operation
3✔
558
        // will fail.
3✔
559
        _, err = b.DecryptAndHash(p[:])
3✔
560
        return err
3✔
561
}
562

563
// GenActTwo generates the second packet (act two) to be sent from the
564
// responder to the initiator. The packet for act two is identical to that of
565
// act one, but then results in a different ECDH operation between the
566
// initiator's and responder's ephemeral keys.
567
//
568
//        <- e, ee
569
func (b *Machine) GenActTwo() ([ActTwoSize]byte, error) {
3✔
570
        var actTwo [ActTwoSize]byte
3✔
571

3✔
572
        // e
3✔
573
        localEphemeral, err := b.ephemeralGen()
3✔
574
        if err != nil {
3✔
575
                return actTwo, err
×
576
        }
×
577
        b.localEphemeral = &keychain.PrivKeyECDH{
3✔
578
                PrivKey: localEphemeral,
3✔
579
        }
3✔
580

3✔
581
        ephemeral := localEphemeral.PubKey().SerializeCompressed()
3✔
582
        b.mixHash(localEphemeral.PubKey().SerializeCompressed())
3✔
583

3✔
584
        // ee
3✔
585
        s, err := ecdh(b.remoteEphemeral, b.localEphemeral)
3✔
586
        if err != nil {
3✔
587
                return actTwo, err
×
588
        }
×
589
        b.mixKey(s)
3✔
590

3✔
591
        authPayload := b.EncryptAndHash([]byte{})
3✔
592

3✔
593
        actTwo[0] = HandshakeVersion
3✔
594
        copy(actTwo[1:34], ephemeral)
3✔
595
        copy(actTwo[34:], authPayload)
3✔
596

3✔
597
        return actTwo, nil
3✔
598
}
599

600
// RecvActTwo processes the second packet (act two) sent from the responder to
601
// the initiator. A successful processing of this packet authenticates the
602
// initiator to the responder.
603
func (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) error {
3✔
604
        var (
3✔
605
                err error
3✔
606
                e   [33]byte
3✔
607
                p   [16]byte
3✔
608
        )
3✔
609

3✔
610
        // If the handshake version is unknown, then the handshake fails
3✔
611
        // immediately.
3✔
612
        if actTwo[0] != HandshakeVersion {
3✔
UNCOV
613
                return fmt.Errorf("act two: invalid handshake version: %v, "+
×
UNCOV
614
                        "only %v is valid, msg=%x", actTwo[0], HandshakeVersion,
×
UNCOV
615
                        actTwo[:])
×
UNCOV
616
        }
×
617

618
        copy(e[:], actTwo[1:34])
3✔
619
        copy(p[:], actTwo[34:])
3✔
620

3✔
621
        // e
3✔
622
        b.remoteEphemeral, err = btcec.ParsePubKey(e[:])
3✔
623
        if err != nil {
3✔
UNCOV
624
                return err
×
UNCOV
625
        }
×
626
        b.mixHash(b.remoteEphemeral.SerializeCompressed())
3✔
627

3✔
628
        // ee
3✔
629
        s, err := ecdh(b.remoteEphemeral, b.localEphemeral)
3✔
630
        if err != nil {
3✔
631
                return err
×
632
        }
×
633
        b.mixKey(s)
3✔
634

3✔
635
        _, err = b.DecryptAndHash(p[:])
3✔
636
        return err
3✔
637
}
638

639
// GenActThree creates the final (act three) packet of the handshake. Act three
640
// is to be sent from the initiator to the responder. The purpose of act three
641
// is to transmit the initiator's public key under strong forward secrecy to
642
// the responder. This act also includes the final ECDH operation which yields
643
// the final session.
644
//
645
//        -> s, se
646
func (b *Machine) GenActThree() ([ActThreeSize]byte, error) {
3✔
647
        var actThree [ActThreeSize]byte
3✔
648

3✔
649
        ourPubkey := b.localStatic.PubKey().SerializeCompressed()
3✔
650
        ciphertext := b.EncryptAndHash(ourPubkey)
3✔
651

3✔
652
        s, err := ecdh(b.remoteEphemeral, b.localStatic)
3✔
653
        if err != nil {
3✔
654
                return actThree, err
×
655
        }
×
656
        b.mixKey(s)
3✔
657

3✔
658
        authPayload := b.EncryptAndHash([]byte{})
3✔
659

3✔
660
        actThree[0] = HandshakeVersion
3✔
661
        copy(actThree[1:50], ciphertext)
3✔
662
        copy(actThree[50:], authPayload)
3✔
663

3✔
664
        // With the final ECDH operation complete, derive the session sending
3✔
665
        // and receiving keys.
3✔
666
        b.split()
3✔
667

3✔
668
        return actThree, nil
3✔
669
}
670

671
// RecvActThree processes the final act (act three) sent from the initiator to
672
// the responder. After processing this act, the responder learns of the
673
// initiator's static public key. Decryption of the static key serves to
674
// authenticate the initiator to the responder.
675
func (b *Machine) RecvActThree(actThree [ActThreeSize]byte) error {
3✔
676
        var (
3✔
677
                err error
3✔
678
                s   [33 + 16]byte
3✔
679
                p   [16]byte
3✔
680
        )
3✔
681

3✔
682
        // If the handshake version is unknown, then the handshake fails
3✔
683
        // immediately.
3✔
684
        if actThree[0] != HandshakeVersion {
3✔
UNCOV
685
                return fmt.Errorf("act three: invalid handshake version: %v, "+
×
UNCOV
686
                        "only %v is valid, msg=%x", actThree[0], HandshakeVersion,
×
UNCOV
687
                        actThree[:])
×
UNCOV
688
        }
×
689

690
        copy(s[:], actThree[1:33+16+1])
3✔
691
        copy(p[:], actThree[33+16+1:])
3✔
692

3✔
693
        // s
3✔
694
        remotePub, err := b.DecryptAndHash(s[:])
3✔
695
        if err != nil {
3✔
UNCOV
696
                return err
×
UNCOV
697
        }
×
698
        b.remoteStatic, err = btcec.ParsePubKey(remotePub)
3✔
699
        if err != nil {
3✔
700
                return err
×
701
        }
×
702

703
        // se
704
        se, err := ecdh(b.remoteStatic, b.localEphemeral)
3✔
705
        if err != nil {
3✔
706
                return err
×
707
        }
×
708
        b.mixKey(se)
3✔
709

3✔
710
        if _, err := b.DecryptAndHash(p[:]); err != nil {
3✔
711
                return err
×
712
        }
×
713

714
        // With the final ECDH operation complete, derive the session sending
715
        // and receiving keys.
716
        b.split()
3✔
717

3✔
718
        return nil
3✔
719
}
720

721
// split is the final wrap-up act to be executed at the end of a successful
722
// three act handshake. This function creates two internal cipherState
723
// instances: one which is used to encrypt messages from the initiator to the
724
// responder, and another which is used to encrypt message for the opposite
725
// direction.
726
func (b *Machine) split() {
3✔
727
        var (
3✔
728
                empty   []byte
3✔
729
                sendKey [32]byte
3✔
730
                recvKey [32]byte
3✔
731
        )
3✔
732

3✔
733
        h := hkdf.New(sha256.New, empty, b.chainingKey[:], empty)
3✔
734

3✔
735
        // If we're the initiator the first 32 bytes are used to encrypt our
3✔
736
        // messages and the second 32-bytes to decrypt their messages. For the
3✔
737
        // responder the opposite is true.
3✔
738
        if b.initiator {
6✔
739
                h.Read(sendKey[:])
3✔
740
                b.sendCipher = cipherState{}
3✔
741
                b.sendCipher.InitializeKeyWithSalt(b.chainingKey, sendKey)
3✔
742

3✔
743
                h.Read(recvKey[:])
3✔
744
                b.recvCipher = cipherState{}
3✔
745
                b.recvCipher.InitializeKeyWithSalt(b.chainingKey, recvKey)
3✔
746
        } else {
6✔
747
                h.Read(recvKey[:])
3✔
748
                b.recvCipher = cipherState{}
3✔
749
                b.recvCipher.InitializeKeyWithSalt(b.chainingKey, recvKey)
3✔
750

3✔
751
                h.Read(sendKey[:])
3✔
752
                b.sendCipher = cipherState{}
3✔
753
                b.sendCipher.InitializeKeyWithSalt(b.chainingKey, sendKey)
3✔
754
        }
3✔
755
}
756

757
// WriteMessage encrypts and buffers the next message p. The ciphertext of the
758
// message is prepended with an encrypt+auth'd length which must be used as the
759
// AD to the AEAD construction when being decrypted by the other side.
760
//
761
// NOTE: This DOES NOT write the message to the wire, it should be followed by a
762
// call to Flush to ensure the message is written.
763
func (b *Machine) WriteMessage(p []byte) error {
3✔
764
        // The total length of each message payload including the MAC size
3✔
765
        // payload exceed the largest number encodable within a 16-bit unsigned
3✔
766
        // integer.
3✔
767
        if len(p) > math.MaxUint16 {
3✔
UNCOV
768
                return ErrMaxMessageLengthExceeded
×
UNCOV
769
        }
×
770

771
        // If a prior message was written but it hasn't been fully flushed,
772
        // return an error as we only support buffering of one message at a
773
        // time.
774
        if len(b.nextHeaderSend) > 0 || len(b.nextBodySend) > 0 {
3✔
775
                return ErrMessageNotFlushed
×
776
        }
×
777

778
        // The full length of the packet is only the packet length, and does
779
        // NOT include the MAC.
780
        fullLength := uint16(len(p))
3✔
781

3✔
782
        binary.BigEndian.PutUint16(b.pktLenBuffer[:], fullLength)
3✔
783

3✔
784
        headerBufInterface := headerBufferPool.Get()
3✔
785
        headerBuf, ok := headerBufInterface.(*[]byte)
3✔
786
        if !ok {
3✔
NEW
787
                b.releaseBuffers()
×
NEW
788
                return fmt.Errorf("headerBufferPool returned unexpected "+
×
NEW
789
                        "type: %T", headerBufInterface)
×
NEW
790
        }
×
791
        b.pooledHeaderBuf = headerBuf
3✔
792

3✔
793
        bodyBufInterface := bodyBufferPool.Get()
3✔
794
        bodyBuf, ok := bodyBufInterface.(*[]byte)
3✔
795
        if !ok {
3✔
NEW
796
                b.releaseBuffers()
×
NEW
797
                return fmt.Errorf("bodyBufferPool returned unexpected "+
×
NEW
798
                        "type: %T", bodyBufInterface)
×
NEW
799
        }
×
800
        b.pooledBodyBuf = bodyBuf
3✔
801

3✔
802
        // First, generate the encrypted+MAC'd length prefix for the packet. We
3✔
803
        // pass our pooled buffer as the cipherText (dst) parameter.
3✔
804
        b.nextHeaderSend = b.sendCipher.Encrypt(
3✔
805
                nil, *b.pooledHeaderBuf, b.pktLenBuffer[:],
3✔
806
        )
3✔
807

3✔
808
        // Finally, generate the encrypted packet itself. We pass our pooled
3✔
809
        // buffer as the cipherText (dst) parameter.
3✔
810
        b.nextBodySend = b.sendCipher.Encrypt(nil, *b.pooledBodyBuf, p)
3✔
811

3✔
812
        return nil
3✔
813
}
814

815
// Flush attempts to write a message buffered using WriteMessage to the provided
816
// io.Writer. If no buffered message exists, this will result in a NOP.
817
// Otherwise, it will continue to write the remaining bytes, picking up where
818
// the byte stream left off in the event of a partial write. The number of bytes
819
// returned reflects the number of plaintext bytes in the payload, and does not
820
// account for the overhead of the header or MACs.
821
//
822
// NOTE: It is safe to call this method again iff a timeout error is returned.
823
func (b *Machine) Flush(w io.Writer) (int, error) {
3✔
824
        // First, write out the pending header bytes, if any exist. Any header
3✔
825
        // bytes written will not count towards the total amount flushed.
3✔
826
        if len(b.nextHeaderSend) > 0 {
6✔
827
                // Write any remaining header bytes and shift the slice to point
3✔
828
                // to the next segment of unwritten bytes. If an error is
3✔
829
                // encountered, we can continue to write the header from where
3✔
830
                // we left off on a subsequent call to Flush.
3✔
831
                n, err := w.Write(b.nextHeaderSend)
3✔
832
                b.nextHeaderSend = b.nextHeaderSend[n:]
3✔
833
                if err != nil {
3✔
UNCOV
834
                        return 0, err
×
UNCOV
835
                }
×
836
        }
837

838
        // Next, write the pending body bytes, if any exist. Only the number of
839
        // bytes written that correspond to the ciphertext will be included in
840
        // the total bytes written, bytes written as part of the MAC will not be
841
        // counted.
842
        var nn int
3✔
843
        if len(b.nextBodySend) > 0 {
6✔
844
                // Write out all bytes excluding the mac and shift the body
3✔
845
                // slice depending on the number of actual bytes written.
3✔
846
                n, err := w.Write(b.nextBodySend)
3✔
847
                b.nextBodySend = b.nextBodySend[n:]
3✔
848

3✔
849
                // If we partially or fully wrote any of the body's MAC, we'll
3✔
850
                // subtract that contribution from the total amount flushed to
3✔
851
                // preserve the abstraction of returning the number of plaintext
3✔
852
                // bytes written by the connection.
3✔
853
                //
3✔
854
                // There are three possible scenarios we must handle to ensure
3✔
855
                // the returned value is correct. In the first case, the write
3✔
856
                // straddles both payload and MAC bytes, and we must subtract
3✔
857
                // the number of MAC bytes written from n. In the second, only
3✔
858
                // payload bytes are written, thus we can return n unmodified.
3✔
859
                // The final scenario pertains to the case where only MAC bytes
3✔
860
                // are written, none of which count towards the total.
3✔
861
                //
3✔
862
                //                 |-----------Payload------------|----MAC----|
3✔
863
                // Straddle:       S---------------------------------E--------0
3✔
864
                // Payload-only:   S------------------------E-----------------0
3✔
865
                // MAC-only:                                        S-------E-0
3✔
866
                start, end := n+len(b.nextBodySend), len(b.nextBodySend)
3✔
867
                switch {
3✔
868

869
                // Straddles payload and MAC bytes, subtract number of MAC bytes
870
                // written from the actual number written.
871
                case start > macSize && end <= macSize:
3✔
872
                        nn = n - (macSize - end)
3✔
873

874
                // Only payload bytes are written, return n directly.
875
                case start > macSize && end > macSize:
3✔
876
                        nn = n
3✔
877

878
                // Only MAC bytes are written, return 0 bytes written.
UNCOV
879
                default:
×
880
                }
881

882
                if err != nil {
6✔
883
                        return nn, err
3✔
884
                }
3✔
885
        }
886

887
        // If both header and body have been fully flushed, release the pooled
888
        // buffers back to their pools.
889
        if len(b.nextHeaderSend) == 0 && len(b.nextBodySend) == 0 {
6✔
890
                b.releaseBuffers()
3✔
891
        }
3✔
892

893
        return nn, nil
3✔
894
}
895

896
// releaseBuffers returns the pooled buffers back to their respective pools
897
// and clears the references.
898
func (b *Machine) releaseBuffers() {
3✔
899
        if b.pooledHeaderBuf != nil {
6✔
900
                *b.pooledHeaderBuf = (*b.pooledHeaderBuf)[:0]
3✔
901
                headerBufferPool.Put(b.pooledHeaderBuf)
3✔
902
                b.pooledHeaderBuf = nil
3✔
903
        }
3✔
904

905
        if b.pooledBodyBuf != nil {
6✔
906
                *b.pooledBodyBuf = (*b.pooledBodyBuf)[:0]
3✔
907
                bodyBufferPool.Put(b.pooledBodyBuf)
3✔
908
                b.pooledBodyBuf = nil
3✔
909
        }
3✔
910

911
        b.nextHeaderSend = nil
3✔
912
        b.nextBodySend = nil
3✔
913
}
914

915
// ReadMessage attempts to read the next message from the passed io.Reader. In
916
// the case of an authentication error, a non-nil error is returned.
917
func (b *Machine) ReadMessage(r io.Reader) ([]byte, error) {
3✔
918
        pktLen, err := b.ReadHeader(r)
3✔
919
        if err != nil {
3✔
UNCOV
920
                return nil, err
×
UNCOV
921
        }
×
922

923
        buf := make([]byte, pktLen)
3✔
924
        return b.ReadBody(r, buf)
3✔
925
}
926

927
// ReadHeader attempts to read the next message header from the passed
928
// io.Reader. The header contains the length of the next body including
929
// additional overhead of the MAC. In the case of an authentication error, a
930
// non-nil error is returned.
931
//
932
// NOTE: This method SHOULD NOT be used in the case that the io.Reader may be
933
// adversarial and induce long delays. If the caller needs to set read deadlines
934
// appropriately, it is preferred that they use the split ReadHeader and
935
// ReadBody methods so that the deadlines can be set appropriately on each.
936
func (b *Machine) ReadHeader(r io.Reader) (uint32, error) {
3✔
937
        _, err := io.ReadFull(r, b.nextCipherHeader[:])
3✔
938
        if err != nil {
6✔
939
                return 0, err
3✔
940
        }
3✔
941

942
        // Attempt to decrypt+auth the packet length present in the stream.
943
        //
944
        // By passing in `nextCipherHeader` as the destination, we avoid making
945
        // the library allocate a new buffer to decode the plaintext.
946
        pktLenBytes, err := b.recvCipher.Decrypt(
3✔
947
                nil, b.nextCipherHeader[:0], b.nextCipherHeader[:],
3✔
948
        )
3✔
949
        if err != nil {
3✔
UNCOV
950
                return 0, err
×
UNCOV
951
        }
×
952

953
        // Compute the packet length that we will need to read off the wire.
954
        pktLen := uint32(binary.BigEndian.Uint16(pktLenBytes)) + macSize
3✔
955

3✔
956
        return pktLen, nil
3✔
957
}
958

959
// ReadBody attempts to ready the next message body from the passed io.Reader.
960
// The provided buffer MUST be the length indicated by the packet length
961
// returned by the preceding call to ReadHeader. In the case of an
962
// authentication error, a non-nil error is returned.
963
func (b *Machine) ReadBody(r io.Reader, buf []byte) ([]byte, error) {
3✔
964
        // Next, using the length read from the packet header, read the
3✔
965
        // encrypted packet itself into the buffer allocated by the read
3✔
966
        // pool.
3✔
967
        _, err := io.ReadFull(r, buf)
3✔
968
        if err != nil {
3✔
969
                return nil, err
×
970
        }
×
971

972
        // Finally, decrypt the message held in the buffer, and return a new
973
        // byte slice containing the plaintext.
974
        //
975
        // By passing in the buf (the ciphertext) as the first argument, we end
976
        // up re-using it as we don't force the library to allocate a new
977
        // buffer to decode the plaintext.
978
        return b.recvCipher.Decrypt(nil, buf[:0], buf)
3✔
979
}
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