forked from lightningnetwork/lnd
/
conn.go
363 lines (304 loc) · 9.92 KB
/
conn.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
package lndc
import (
"bytes"
"crypto/cipher"
"crypto/hmac"
"encoding/binary"
"fmt"
"net"
"time"
"github.com/btcsuite/fastsha256"
"github.com/codahale/chacha20poly1305"
"github.com/roasbeef/btcutil"
"github.com/roasbeef/btcd/btcec"
)
// Conn...
type LNDConn struct {
RemotePub *btcec.PublicKey
RemoteLNId [16]byte
myNonceInt uint64
remoteNonceInt uint64
// If Authed == false, the remotePub is the EPHEMERAL key.
// once authed == true, remotePub is who you're actually talking to.
Authed bool
// chachaStream saves some time as you don't have to init it with
// the session key every time. Make SessionKey redundant; remove later.
chachaStream cipher.AEAD
// ViaPbx specifies whether this is a direct TCP connection or an
// encapsulated PBX connection.
// If going ViaPbx, Cn isn't used channels are used for Read() and
// Write(), which are filled by the PBXhandler.
ViaPbx bool
PbxIncoming chan []byte
PbxOutgoing chan []byte
version uint8
readBuf bytes.Buffer
Conn net.Conn
}
// NewConn...
func NewConn(conn net.Conn) *LNDConn {
return &LNDConn{Conn: conn}
}
// Dial...
func (c *LNDConn) Dial(
myId *btcec.PrivateKey, address string, remoteId []byte) error {
var err error
if !c.ViaPbx {
if c.Conn != nil {
return fmt.Errorf("connection already established")
}
// First, open the TCP connection itself.
c.Conn, err = net.Dial("tcp", address)
if err != nil {
return err
}
}
// Before dialing out to the remote host, verify that `remoteId` is either
// a pubkey or a pubkey hash.
if len(remoteId) != 33 && len(remoteId) != 20 {
return fmt.Errorf("must supply either remote pubkey or " +
"pubkey hash")
}
// Calc remote LNId; need this for creating pbx connections just because
// LNid is in the struct does not mean it's authed!
if len(remoteId) == 20 {
copy(c.RemoteLNId[:], remoteId[:16])
} else {
theirAdr := btcutil.Hash160(remoteId)
copy(c.RemoteLNId[:], theirAdr[:16])
}
// Make up an ephemeral keypair for this session.
ourEphemeralPriv, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
return err
}
ourEphemeralPub := ourEphemeralPriv.PubKey()
// Send 1. Send my ephemeral pubkey. Can add version bits.
if _, err = writeClear(c.Conn, ourEphemeralPub.SerializeCompressed()); err != nil {
return err
}
// Read, then deserialize their ephemeral public key.
theirEphPubBytes, err := readClear(c.Conn)
if err != nil {
return err
}
theirEphPub, err := btcec.ParsePubKey(theirEphPubBytes, btcec.S256())
if err != nil {
return err
}
// Do non-interactive diffie with ephemeral pubkeys. Sha256 for good
// luck.
sessionKey := fastsha256.Sum256(
btcec.GenerateSharedSecret(ourEphemeralPriv, theirEphPub),
)
// Now that we've derive the session key, we can initialize the
// chacha20poly1305 AEAD instance which will be used for the remainder of
// the session.
c.chachaStream, err = chacha20poly1305.New(sessionKey[:])
if err != nil {
return err
}
c.myNonceInt = 1 << 63
c.remoteNonceInt = 0
c.RemotePub = theirEphPub
c.Authed = false
// Session is now open and confidential but not yet authenticated...
// So auth!
if len(remoteId) == 20 {
// Only know pubkey hash (20 bytes).
err = c.authPKH(myId, remoteId, ourEphemeralPub.SerializeCompressed())
} else {
// Must be 33 byte pubkey.
err = c.authPubKey(myId, remoteId, ourEphemeralPub.SerializeCompressed())
}
if err != nil {
return err
}
return nil
}
// authPubKey...
func (c *LNDConn) authPubKey(
myId *btcec.PrivateKey, remotePubBytes, localEphPubBytes []byte) error {
if c.Authed {
return fmt.Errorf("%s already authed", c.RemotePub)
}
// Since we already know their public key, we can immediately generate
// the DH proof without an additional round-trip.
theirPub, err := btcec.ParsePubKey(remotePubBytes, btcec.S256())
if err != nil {
return err
}
theirPKH := btcutil.Hash160(remotePubBytes)
idDH := fastsha256.Sum256(btcec.GenerateSharedSecret(myId, theirPub))
myDHproof := btcutil.Hash160(append(c.RemotePub.SerializeCompressed(), idDH[:]...))
// Send over the 73 byte authentication message: my pubkey, their
// pubkey hash, DH proof.
var authMsg [73]byte
copy(authMsg[:33], myId.PubKey().SerializeCompressed())
copy(authMsg[33:], theirPKH)
copy(authMsg[53:], myDHproof)
if _, err = c.Conn.Write(authMsg[:]); err != nil {
return nil
}
// Await, their response. They should send only the 20-byte DH proof.
resp := make([]byte, 20)
_, err = c.Conn.Read(resp)
if err != nil {
return err
}
// Verify that their proof matches our locally computed version.
theirDHproof := btcutil.Hash160(append(localEphPubBytes, idDH[:]...))
if !hmac.Equal(resp, theirDHproof) {
return fmt.Errorf("invalid DH proof %x", theirDHproof)
}
// Proof checks out, auth complete.
c.RemotePub = theirPub
theirAdr := btcutil.Hash160(theirPub.SerializeCompressed())
copy(c.RemoteLNId[:], theirAdr[:16])
c.Authed = true
return nil
}
// authPKH...
func (c *LNDConn) authPKH(
myId *btcec.PrivateKey, theirPKH, localEphPubBytes []byte) error {
if c.Authed {
return fmt.Errorf("%s already authed", c.RemotePub)
}
if len(theirPKH) != 20 {
return fmt.Errorf("remote PKH must be 20 bytes, got %d",
len(theirPKH))
}
// Send 53 bytes: our pubkey, and the remote's pubkey hash.
var greetingMsg [53]byte
copy(greetingMsg[:33], myId.PubKey().SerializeCompressed())
copy(greetingMsg[33:], theirPKH)
if _, err := c.Conn.Write(greetingMsg[:]); err != nil {
return err
}
// Wait for their response.
// TODO(tadge): add timeout here
// * NOTE(roasbeef): read timeout should be set on the underlying
// net.Conn.
resp := make([]byte, 53)
if _, err := c.Conn.Read(resp); err != nil {
return err
}
// Parse their long-term public key, and generate the DH proof.
theirPub, err := btcec.ParsePubKey(resp[:33], btcec.S256())
if err != nil {
return err
}
idDH := fastsha256.Sum256(btcec.GenerateSharedSecret(myId, theirPub))
theirDHproof := btcutil.Hash160(append(localEphPubBytes, idDH[:]...))
// Verify that their DH proof matches the one we just generated.
if !hmac.Equal(resp[33:], theirDHproof) {
return fmt.Errorf("Invalid DH proof %x", theirDHproof)
}
// If their DH proof checks out, then send our own.
myDHproof := btcutil.Hash160(append(c.RemotePub.SerializeCompressed(), idDH[:]...))
if _, err = c.Conn.Write(myDHproof); err != nil {
return err
}
// Proof sent, auth complete.
c.RemotePub = theirPub
theirAdr := btcutil.Hash160(theirPub.SerializeCompressed())
copy(c.RemoteLNId[:], theirAdr[:16])
c.Authed = true
return nil
}
// Read reads data from the connection.
// Read can be made to time out and return a Error with Timeout() == true
// after a fixed time limit; see SetDeadline and SetReadDeadline.
// Part of the net.Conn interface.
func (c *LNDConn) Read(b []byte) (n int, err error) {
// In order to reconcile the differences between the record abstraction
// of our AEAD connection, and the stream abstraction of TCP, we maintain
// an intermediate read buffer. If this buffer becomes depleated, then
// we read the next record, and feed it into the buffer. Otherwise, we
// read directly from the buffer.
if c.readBuf.Len() == 0 {
// The buffer is empty, so read the next cipher text.
ctext, err := readClear(c.Conn)
if err != nil {
return 0, err
}
// Encode the current remote nonce, so we can use it to decrypt
// the cipher text.
var nonceBuf [8]byte
binary.BigEndian.PutUint64(nonceBuf[:], c.remoteNonceInt)
c.remoteNonceInt++ // increment remote nonce, no matter what...
msg, err := c.chachaStream.Open(nil, nonceBuf[:], ctext, nil)
if err != nil {
return 0, err
}
if _, err := c.readBuf.Write(msg); err != nil {
return 0, err
}
}
return c.readBuf.Read(b)
}
// Write writes data to the connection.
// Write can be made to time out and return a Error with Timeout() == true
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
// Part of the net.Conn interface.
func (c *LNDConn) Write(b []byte) (n int, err error) {
if b == nil {
return 0, fmt.Errorf("write to %x nil", c.RemoteLNId)
}
// first encrypt message with shared key
var nonceBuf [8]byte
binary.BigEndian.PutUint64(nonceBuf[:], c.myNonceInt)
c.myNonceInt++ // increment mine
ctext := c.chachaStream.Seal(nil, nonceBuf[:], b, nil)
if err != nil {
return 0, err
}
if len(ctext) > 65530 {
return 0, fmt.Errorf("Write to %x too long, %d bytes",
c.RemoteLNId, len(ctext))
}
// use writeClear to prepend length / destination header
return writeClear(c.Conn, ctext)
}
// Close closes the connection.
// Any blocked Read or Write operations will be unblocked and return errors.
// Part of the net.Conn interface.
func (c *LNDConn) Close() error {
c.myNonceInt = 0
c.remoteNonceInt = 0
c.RemotePub = nil
return c.Conn.Close()
}
// LocalAddr returns the local network address.
// Part of the net.Conn interface.
// If PBX reports address of pbx host.
func (c *LNDConn) LocalAddr() net.Addr {
return c.Conn.LocalAddr()
}
// RemoteAddr returns the remote network address.
// Part of the net.Conn interface.
func (c *LNDConn) RemoteAddr() net.Addr {
return c.Conn.RemoteAddr()
}
// SetDeadline sets the read and write deadlines associated
// with the connection. It is equivalent to calling both
// SetReadDeadline and SetWriteDeadline.
// Part of the net.Conn interface.
func (c *LNDConn) SetDeadline(t time.Time) error {
return c.Conn.SetDeadline(t)
}
// SetReadDeadline sets the deadline for future Read calls.
// A zero value for t means Read will not time out.
// Part of the net.Conn interface.
func (c *LNDConn) SetReadDeadline(t time.Time) error {
return c.Conn.SetReadDeadline(t)
}
// SetWriteDeadline sets the deadline for future Write calls.
// Even if write times out, it may return n > 0, indicating that
// some of the data was successfully written.
// A zero value for t means Write will not time out.
// Part of the net.Conn interface.
func (c *LNDConn) SetWriteDeadline(t time.Time) error {
return c.Conn.SetWriteDeadline(t)
}
var _ net.Conn = (*LNDConn)(nil)