diff --git a/lndc/conn.go b/lndc/conn.go index 3e0ccd514..b0182e681 100644 --- a/lndc/conn.go +++ b/lndc/conn.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "log" "math" "net" "time" @@ -29,15 +30,31 @@ type Conn struct { // A compile-time assertion to ensure that Conn meets the net.Conn interface. var _ net.Conn = (*Conn)(nil) +var Noise_XK bool // Dial attempts to establish an encrypted+authenticated connection with the // remote peer located at address which has remotePub as its long-term static // public key. In the case of a handshake failure, the connection is closed and // a non-nil error is returned. -func Dial(localPriv *koblitz.PrivateKey, ipAddr string, remotePKH string, +func Dial(localPriv *btcec.PrivateKey, ipAddr string, remoteAddress string, dialer func(string, string) (net.Conn, error)) (*Conn, error) { + + var remotePKH string + var remotePK [33]byte + if remoteAddress[0:3] == "ln1" { // its a remote PKH + remotePKH = remoteAddress + } else if len(remoteAddress) == 33 { // remotePK + temp := []byte(remoteAddress) + copy(remotePK[:], temp) + } var conn net.Conn var err error + var empty [33]byte + if remotePK != empty { + log.Println("Connecting via Noise_XK since we know remotePK") + Noise_XK = true + SetConsts() + } conn, err = dialer("tcp", ipAddr) logging.Info("ipAddr is", ipAddr) if err != nil { @@ -48,9 +65,8 @@ func Dial(localPriv *koblitz.PrivateKey, ipAddr string, remotePKH string, conn: conn, noise: NewNoiseMachine(true, localPriv), } - // Initiate the handshake by sending the first act to the receiver. - actOne, err := b.noise.GenActOne() + actOne, err := b.noise.GenActOne(remotePK) if err != nil { b.conn.Close() return nil, err @@ -69,22 +85,30 @@ func Dial(localPriv *koblitz.PrivateKey, ipAddr string, remotePKH string, // remotePub), then read the second act after which we'll be able to // send our static public key to the remote peer with strong forward // secrecy. - var actTwo [ActTwoSize]byte + actTwo := make([]byte, ActTwoSize) if _, err := io.ReadFull(conn, actTwo[:]); err != nil { b.conn.Close() return nil, err } - s, err := b.noise.RecvActTwo(actTwo) - if err != nil { - b.conn.Close() - return nil, err + if !Noise_XK { + remotePK, err = b.noise.RecvActTwo(actTwo) + if err != nil { + b.conn.Close() + return nil, err + } + } else { + if _, err := b.noise.RecvActTwo(actTwo); err != nil { + b.conn.Close() + return nil, err + } } - - logging.Info("Received pubkey", s) - if lnutil.LitAdrFromPubkey(s) != remotePKH { + logging.Infoln("Received pubkey", remotePK) + if lnutil.LitAdrFromPubkey(remotePK) != remotePKH && !Noise_XK { + // for noise_XK dont check PKH and PK because we'd have already checked this + // the last time we connected to this guy return nil, fmt.Errorf("Remote PKH doesn't match. Quitting!") } - logging.Infof("Received PKH %s matches", lnutil.LitAdrFromPubkey(s)) + logging.Infof("Received PKH %s matches", lnutil.LitAdrFromPubkey(remotePK)) // Finally, complete the handshake by sending over our encrypted static // key and execute the final ECDH operation. diff --git a/lndc/listener.go b/lndc/listener.go index 2fd348bae..bfb9e0234 100644 --- a/lndc/listener.go +++ b/lndc/listener.go @@ -3,6 +3,7 @@ package lndc import ( "errors" "io" + "log" "net" "time" @@ -111,12 +112,17 @@ func (l *Listener) doHandshake(conn net.Conn) { // Attempt to carry out the first act of the handshake protocol. If the // connecting node doesn't know our long-term static public key, then // this portion will fail with a non-nil error. - var actOne [ActOneSize]byte + actOne := make([]byte, ActOneSize) + log.Println("Handshake Version", HandshakeVersion) if _, err := io.ReadFull(conn, actOne[:]); err != nil { lndcConn.conn.Close() l.rejectConn(err) return } + if actOne[0] == 0 { // remote node wants to connect via XK + HandshakeVersion = byte(0) + ActTwoSize = 50 + } // no need for else as default covers XX if err := lndcConn.noise.RecvActOne(actOne); err != nil { lndcConn.conn.Close() l.rejectConn(err) @@ -124,7 +130,7 @@ func (l *Listener) doHandshake(conn net.Conn) { } // Next, progress the handshake processes by sending over our ephemeral // key for the session along with an authenticating tag. - actTwo, err := lndcConn.noise.GenActTwo() + actTwo, err := lndcConn.noise.GenActTwo(HandshakeVersion) if err != nil { lndcConn.conn.Close() l.rejectConn(err) @@ -150,7 +156,7 @@ func (l *Listener) doHandshake(conn net.Conn) { // Finally, finish the handshake processes by reading and decrypting // the connection peer's static public key. If this succeeds then both // sides have mutually authenticated each other. - var actThree [ActThreeSize]byte + actThree := make([]byte, ActThreeSize) if _, err := io.ReadFull(conn, actThree[:]); err != nil { lndcConn.conn.Close() l.rejectConn(err) diff --git a/lndc/noise.go b/lndc/noise.go index 59c14c55f..4cb114ec3 100644 --- a/lndc/noise.go +++ b/lndc/noise.go @@ -4,8 +4,8 @@ import ( "crypto/cipher" "crypto/sha256" "encoding/binary" - "fmt" "errors" + "fmt" "io" "math" "time" @@ -316,10 +316,19 @@ func EphemeralGenerator(gen func() (*koblitz.PrivateKey, error)) func(*Machine) // INITIATOR -> e RESPONDER // INITIATOR <- e, ee, s, es RESPONDER // INITIATOR -> s, se RESPONDER +// The protocol has the following steps involved: +// XK(s, rs): +// INITIATOR <- s +// INITIATOR -> e, es RESPONDER +// INITIATOR <- e, ee RESPONDER +// INITIATOR -> s, se RESPONDER // s refers to the static key (or public key) belonging to an entity // e refers to the ephemeral key // e, ee, es refer to a DH exchange between the initiator's key pair and the // responder's key pair. The letters e and s hold the same meaning as before. +// lit uses Noise_XX to connect with nodes that it does not know of and uses +// Noise_XK for nodes that it has previously connected to. This saves 33 bytes +// in Act Two. type Machine struct { sendCipher cipherState @@ -373,12 +382,13 @@ func NewNoiseMachine(initiator bool, localStatic *koblitz.PrivateKey, return m } -const ( +var ( // HandshakeVersion is the expected version of the lndc handshake. // Any messages that carry a different version will cause the handshake // to abort immediately. - HandshakeVersion = byte(1) // TODO: add support for noise_XK (brontide) as well - + HandshakeVersion = byte(1) + // noise_XX HandshakeVersion = 1 + // noise_XK HandshakeVersion = 0 // ActOneSize is the size of the packet sent from initiator to // responder in ActOne. The packet consists of a handshake version, an // ephemeral key in compressed format, and a 16-byte poly1305 tag. @@ -403,18 +413,25 @@ const ( ActThreeSize = 66 ) +func SetConsts() { + HandshakeVersion = byte(0) // Noise_XK's hadnshake version + // ActTwoSize is the size the packet sent from responder to initiator + // in ActTwo. The packet consists of a handshake version, an ephemeral + // key in compressed format and a 16-byte poly1305 tag. + // <- e, ee + // 1 + 33 + 16 + ActTwoSize = 50 +} + // GenActOne generates the initial packet (act one) to be sent from initiator // to responder. During act one the initiator generates an ephemeral key and // hashes it into the handshake digest. Future payloads are encrypted with a key // derived from this result. // -> e -func (b *Machine) GenActOne() ([ActOneSize]byte, error) { - var ( - err error - actOne [ActOneSize]byte - ) - +func (b *Machine) GenActOne(remotePK [33]byte) ([]byte, error) { + var err error + actOne := make([]byte, ActOneSize) // Generate e b.localEphemeral, err = b.ephemeralGen() if err != nil { @@ -426,6 +443,15 @@ func (b *Machine) GenActOne() ([ActOneSize]byte, error) { // Hash it into the handshake digest b.mixHash(e) + if Noise_XK { + b.remoteStatic, err = btcec.ParsePubKey(remotePK[:], btcec.S256()) + if err != nil { + return nil, err + } + // es + s := ecdh(b.remoteStatic, b.localEphemeral) + b.mixKey(s[:]) + } authPayload := b.EncryptAndHash([]byte{}) actOne[0] = HandshakeVersion copy(actOne[1:34], e) @@ -437,16 +463,15 @@ func (b *Machine) GenActOne() ([ActOneSize]byte, error) { // executes the mirrored actions to that of the initiator extending the // handshake digest and deriving a new shared secret based on an ECDH with the // initiator's ephemeral key and responder's static key. -func (b *Machine) RecvActOne(actOne [ActOneSize]byte) error { +func (b *Machine) RecvActOne(actOne []byte) error { var ( err error e [33]byte p [16]byte ) - // If the handshake version is unknown, then the handshake fails // immediately. - if actOne[0] != HandshakeVersion { + if !(actOne[0] == 0 || actOne[0] == 1) { return fmt.Errorf("Act One: invalid handshake version: %v, "+ "only %v is valid, msg=%x", actOne[0], HandshakeVersion, actOne[:]) @@ -462,6 +487,11 @@ func (b *Machine) RecvActOne(actOne [ActOneSize]byte) error { } b.mixHash(b.remoteEphemeral.SerializeCompressed()) + if actOne[0] == 0 { + // es + es := ecdh(b.remoteEphemeral, b.localStatic) + b.mixKey(es) + } _, err = b.DecryptAndHash(p[:]) return err // nil means Act one completed successfully } @@ -469,11 +499,9 @@ func (b *Machine) RecvActOne(actOne [ActOneSize]byte) error { // GenActTwo generates the second packet (act two) to be sent from the // responder to the initiator // <- e, ee, s, es -func (b *Machine) GenActTwo() ([ActTwoSize]byte, error) { - var ( - err error - actTwo [ActTwoSize]byte - ) +func (b *Machine) GenActTwo(HandshakeVersion byte) ([]byte, error) { + var err error + actTwo := make([]byte, ActTwoSize) // e b.localEphemeral, err = b.ephemeralGen() @@ -488,19 +516,27 @@ func (b *Machine) GenActTwo() ([ActTwoSize]byte, error) { ee := ecdh(b.remoteEphemeral, b.localEphemeral) b.mixKey(ee) - // s - s := b.localStatic.PubKey().SerializeCompressed() - b.mixHash(s) - - // es - es := ecdh(b.remoteEphemeral, b.localStatic) - b.mixKey(es) - + if HandshakeVersion == 1 { + // s + s := b.localStatic.PubKey().SerializeCompressed() + b.mixHash(s) + + // es + es := ecdh(b.remoteEphemeral, b.localStatic) + b.mixKey(es) + + authPayload := b.EncryptAndHash([]byte{}) + actTwo[0] = HandshakeVersion + copy(actTwo[1:34], e) + copy(actTwo[34:67], s) + copy(actTwo[67:], authPayload) + // add additional stuff based on what we need + return actTwo, nil + } authPayload := b.EncryptAndHash([]byte{}) actTwo[0] = HandshakeVersion copy(actTwo[1:34], e) - copy(actTwo[34:67], s) - copy(actTwo[67:], authPayload) + copy(actTwo[34:], authPayload) // add additional stuff based on what we need return actTwo, nil } @@ -508,7 +544,7 @@ func (b *Machine) GenActTwo() ([ActTwoSize]byte, error) { // RecvActTwo processes the second packet (act two) sent from the responder to // the initiator. A successful processing of this packet authenticates the // initiator to the responder. -func (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) ([33]byte, error) { +func (b *Machine) RecvActTwo(actTwo []byte) ([33]byte, error) { var ( err error e [33]byte @@ -524,9 +560,14 @@ func (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) ([33]byte, error) { actTwo[:]) } - copy(e[:], actTwo[1:34]) - copy(s[:], actTwo[34:67]) - copy(p[:], actTwo[67:]) + if HandshakeVersion == 0 { + copy(e[:], actTwo[1:34]) + copy(p[:], actTwo[34:]) + } else { + copy(e[:], actTwo[1:34]) + copy(s[:], actTwo[34:67]) + copy(p[:], actTwo[67:]) + } // e b.remoteEphemeral, err = koblitz.ParsePubKey(e[:], koblitz.S256()) @@ -539,17 +580,19 @@ func (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) ([33]byte, error) { ee := ecdh(b.remoteEphemeral, b.localEphemeral) b.mixKey(ee) - // s - b.remoteStatic, err = koblitz.ParsePubKey(s[:], koblitz.S256()) - if err != nil { - return empty, err - } - b.mixHash(b.remoteStatic.SerializeCompressed()) + if HandshakeVersion == 1 { + // s + b.remoteStatic, err = koblitz.ParsePubKey(s[:], koblitz.S256()) + if err != nil { + return empty, err + } + b.mixHash(b.remoteStatic.SerializeCompressed()) - // es - es := ecdh(b.remoteStatic, b.localEphemeral) - b.mixKey(es) + // es + es := ecdh(b.remoteStatic, b.localEphemeral) + b.mixKey(es) + } _, err = b.DecryptAndHash(p[:]) return s, err } @@ -560,8 +603,9 @@ func (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) ([33]byte, error) { // the responder. This act also includes the final ECDH operation which yields // the final session. // -> s, se -func (b *Machine) GenActThree() ([ActThreeSize]byte, error) { - var actThree [ActThreeSize]byte +func (b *Machine) GenActThree() ([]byte, error) { + //var actThree [ActThreeSize]byte + actThree := make([]byte, ActThreeSize) // s s := b.localStatic.PubKey().SerializeCompressed() @@ -587,7 +631,7 @@ func (b *Machine) GenActThree() ([ActThreeSize]byte, error) { // the responder. After processing this act, the responder learns of the // initiator's static public key. Decryption of the static key serves to // authenticate the initiator to the responder. -func (b *Machine) RecvActThree(actThree [ActThreeSize]byte) error { +func (b *Machine) RecvActThree(actThree []byte) error { var ( err error s [49]byte diff --git a/lndc/noise_test.go b/lndc/noise_test.go index a490c4983..b8f2e8a8b 100644 --- a/lndc/noise_test.go +++ b/lndc/noise_test.go @@ -341,7 +341,8 @@ func TestBolt0008TestVectors(t *testing.T) { // act one. This should consist of exactly 50 bytes. We'll assert that // the payload return is _exactly_ the same as what's specified within // the test vectors. - actOne, err := initiator.GenActOne() + var empty [33]byte + actOne, err := initiator.GenActOne(empty) if err != nil { t.Fatalf("unable to generate act one: %v", err) } @@ -366,7 +367,7 @@ func TestBolt0008TestVectors(t *testing.T) { // its contribution to the crypto handshake. We'll also verify that we // produce the _exact_ same byte stream as advertised within the spec's // test vectors. - actTwo, err := responder.GenActTwo() + actTwo, err := responder.GenActTwo(byte(1)) if err != nil { t.Fatalf("unable to generate act two: %v", err) } diff --git a/qln/init.go b/qln/init.go index ca012cff6..c90e7eb16 100644 --- a/qln/init.go +++ b/qln/init.go @@ -131,9 +131,20 @@ func NewLitNode(privKey *[32]byte, path string, trackerURL string, proxyURL stri nd.SubWallet = make(map[uint32]UWallet) - // REFACTORING STUFF nd.PeerMap = map[*lnp2p.Peer]*RemotePeer{} nd.PeerMapMtx = &sync.Mutex{} + nd.KnownPubkeys = make(map[uint32][33]byte) + var empty [33]byte + i := uint32(1) + for { + pubKey, _ := nd.GetPubHostFromPeerIdx(i) + if pubKey == empty { + logging.Infof("Done, tried %d hosts, none matched\n", i-1) + break + } + nd.KnownPubkeys[i] = pubKey + i++ + } return nd, nil } diff --git a/qln/lndb.go b/qln/lndb.go index c7ff01345..d271a0c18 100644 --- a/qln/lndb.go +++ b/qln/lndb.go @@ -143,6 +143,7 @@ type LitNode struct { // The URL from which lit attempts to resolve the LN address TrackerURL string + KnownPubkeys map[uint32][33]byte ChannelMap map[[20]byte][]LinkDesc ChannelMapMtx sync.Mutex AdvTimeout *time.Ticker diff --git a/qln/netio.go b/qln/netio.go index 8624386d0..a03c18896 100644 --- a/qln/netio.go +++ b/qln/netio.go @@ -2,6 +2,15 @@ package qln import ( "fmt" + "net" + "strconv" + "strings" + "time" + + "github.com/mit-dci/lit/bech32" + "github.com/mit-dci/lit/btcutil/btcec" + "github.com/mit-dci/lit/crypto/fastsha256" + "github.com/mit-dci/lit/lndc" "github.com/mit-dci/lit/crypto/koblitz" "github.com/mit-dci/lit/lncore" "github.com/mit-dci/lit/lnutil" @@ -77,16 +86,58 @@ func splitAdrString(adr string) (string, string) { } // DialPeer makes an outgoing connection to another node. -// TODO Remove this. func (nd *LitNode) DialPeer(connectAdr string) error { +func (nd *LitNode) DialPeer(connectAdr string) (uint32, error) { + var err error + + // parse address and get pkh / host / port + who, where := splitAdrString(connectAdr) + + // If we couldn't deduce a URL, look it up on the tracker + if where == "" { + where, _, err = Lookup(who, nd.TrackerURL, nd.ProxyURL) + if err != nil { + return 0, err + } + } + + var remotePK [33]byte + var noisexk bool + for _, pubkey := range nd.KnownPubkeys { + idHash := fastsha256.Sum256(pubkey[:]) + adr := bech32.Encode("ln", idHash[:20]) + if adr == who { + remotePK = pubkey + noisexk = true + } + } + // get my private ID key + idPriv := nd.IdKey() + var newConn *lndc.Conn + // Assign remote connection + if noisexk { + var remotePKdup [33]byte + temp := []byte(string(remotePK[:])) + copy(remotePKdup[:], temp) + newConn, err = lndc.Dial(idPriv, where, string(remotePK[:]), net.Dial) + if err != nil { + return 0, err + } + } else { + newConn, err = lndc.Dial(idPriv, where, who, net.Dial) + if err != nil { + return 0, err + } + } + // if connect is successful, either query for already existing peer index, or + // if the peer is new, make a new index, and save the hostname&port _, err := nd.PeerMan.TryConnectAddress(connectAdr, nil) if err != nil { return err } // TEMP The event handler handles actually setting up the peer in the LitNode - return nil }