From f37956e38e37804aca75238ece9b65eca06cf11f Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 27 Oct 2016 20:40:08 -0700 Subject: [PATCH] routing: update Sphinx API to include r-hash and per-hop-payload This commit modifies both the Sphinx packet generation and processing for recent updates to the API. With the version 1 Sphinx specification, the payment hash is now included in the MACs in order to thwart any potential replay attacks. As a result, any attempts to replay previous HTLC packets MUST re-use the same payment hash, meaning that the first-hop node can simply settle the HTLC immediately, thwarting the attacker. Additionally, within the Sphinx packet, each hop now gets a per-hop payload which contains the necessary details (CTLV value, fee, etc) for the node to successfully forward the payment. This per-hop payload is protected by a packet-wide MAC. --- glide.lock | 17 +++++----- glide.yaml | 1 + lnwire/htlc_addrequest.go | 1 + peer.go | 16 +++++++-- rpcserver.go | 70 +++++++++++++++++++++++++-------------- 5 files changed, 70 insertions(+), 35 deletions(-) diff --git a/glide.lock b/glide.lock index d593f7e7c..d61acf0ef 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 0bdf51a0e40c8cba475333645a769b711e699be36803b02f3b14430642ab79b2 -updated: 2016-10-17T19:44:40.047044856-07:00 +hash: 2106ce14ff53c14d3d0d3d8f34e1cf01c01a79eef409ffe871cd5783b77939c8 +updated: 2016-10-27T20:34:19.347013604-07:00 imports: - name: github.com/aead/chacha20 version: 7e1038a97ad08a9a16cb88ed7a6778b366ba4d99 @@ -61,6 +61,8 @@ imports: version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 subpackages: - spew +- name: github.com/go-errors/errors + version: a41850380601eeb43f4350f7d17c6bbd8944aaf8 - name: github.com/golang/protobuf version: 98fa357170587e470c5f27d3c3ea0947b71eb455 subpackages: @@ -77,7 +79,7 @@ imports: - name: github.com/howeyc/gopass version: f5387c492211eb133053880d23dfae62aa14123d - name: github.com/lightningnetwork/lightning-onion - version: 81647ffa2c5e17c0447d359e1963a54e18be85c4 + version: f38a054899049d1f5bdb6550c17724060384161b - name: github.com/roasbeef/btcd version: baea7691cc3c59480703fe1a3fb5595c838c963c subpackages: @@ -120,7 +122,7 @@ imports: - name: github.com/urfave/cli version: a14d7d367bc02b1f57d88de97926727f2d936387 - name: golang.org/x/crypto - version: 5f31782cfb2b6373211f8f9fbf31283fa234b570 + version: ca7e7f10cb9fd9c1a6ff7f60436c086d73714180 subpackages: - hkdf - nacl/secretbox @@ -131,7 +133,7 @@ imports: - pbkdf2 - ssh/terminal - name: golang.org/x/net - version: 8b4af36cd21a1f85a7484b49feb7c79363106d8e + version: b336a971b799939dd16ae9b1df8334cb8b977c4d subpackages: - context - http2 @@ -141,7 +143,7 @@ imports: - lex/httplex - internal/timeseries - name: golang.org/x/sys - version: 9bb9f0998d48b31547d975974935ae9b48c7a03c + version: c200b10b5d5e122be351b67af224adc6128af5bf subpackages: - unix - name: google.golang.org/grpc @@ -155,7 +157,4 @@ imports: - naming - transport - peer -- name: github.com/go-errors/errors - version: a41850380601eeb43f4350f7d17c6bbd8944aaf8 - testImports: [] diff --git a/glide.yaml b/glide.yaml index 332176845..763b240f5 100644 --- a/glide.yaml +++ b/glide.yaml @@ -57,6 +57,7 @@ import: - package: google.golang.org/grpc version: ^1.0.0 - package: github.com/lightningnetwork/lightning-onion + version: master - package: github.com/grpc-ecosystem/grpc-gateway version: ^1.1.0 - package: github.com/aead/chacha20 diff --git a/lnwire/htlc_addrequest.go b/lnwire/htlc_addrequest.go index 31f4bd37f..451cbb3b9 100644 --- a/lnwire/htlc_addrequest.go +++ b/lnwire/htlc_addrequest.go @@ -57,6 +57,7 @@ type HTLCAddRequest struct { // and the shared secret is fresh, then the node should stip off a layer // of encryption, exposing the next hop to be used in the subsequent // HTLCAddRequest message. + // TODO(roasbeef): can be fixed sized now that v1 Sphinx is "done". OnionBlob []byte } diff --git a/peer.go b/peer.go index 3c8b331c8..2c6a0d05d 100644 --- a/peer.go +++ b/peer.go @@ -1109,13 +1109,24 @@ func (p *peer) handleUpstreamMsg(state *commitmentState, msg lnwire.Message) { p.Disconnect() return } - sphinxPacket, err := state.sphinx.ProcessOnionPacket(onionPkt) + + // Attempt to process the Sphinx packet. We include the payment + // hash of the HTLC as it's authenticated within the Sphinx + // packet itself as associated data in order to thwart attempts + // a replay attacks. In the case of a replay, an attacker is + // *forced* to use the same payment hash twice, thereby losing + // their money entirely. + rHash := htlcPkt.RedemptionHashes[0][:] + sphinxPacket, err := state.sphinx.ProcessOnionPacket(onionPkt, rHash) if err != nil { peerLog.Errorf("unable to process onion pkt: %v", err) p.Disconnect() return } + // TODO(roasbeef): perform sanity checks on per-hop payload + // * time-lock is sane, fee, chain, etc + // We just received an add request from an upstream peer, so we // add it to our state machine, then add the HTLC to our // "settle" list in the event that we know the pre-image @@ -1144,7 +1155,8 @@ func (p *peer) handleUpstreamMsg(state *commitmentState, msg lnwire.Message) { // switch, we'll attach the routing information so the switch // can finalize the circuit. case sphinx.MoreHops: - // TODO(roasbeef): send cancel + error if not in rounting table + // TODO(roasbeef): send cancel + error if not in + // routing table state.pendingCircuits[index] = sphinxPacket default: peerLog.Errorf("mal formed onion packet") diff --git a/rpcserver.go b/rpcserver.go index 7f601bf96..badb27eb0 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -564,16 +564,6 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer) } rpcsLog.Tracef("[sendpayment] selected route: %v", path) - // Generate the raw encoded sphinx packet to be - // included along with the HTLC add message. - // We snip off the first hop from the path as within - // the routing table's star graph, we're always the - // first hop. - sphinxPacket, err := generateSphinxPacket(path[1:]) - if err != nil { - return err - } - // If we're in debug HTLC mode, then all outgoing // HTLC's will pay to the same debug rHash. Otherwise, // we pay to the rHash specified within the RPC @@ -585,6 +575,16 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer) copy(rHash[:], nextPayment.PaymentHash) } + // Generate the raw encoded sphinx packet to be + // included along with the HTLC add message. We snip + // off the first hop from the path as within the + // routing table's star graph, we're always the first + // hop. + sphinxPacket, err := generateSphinxPacket(path[1:], rHash[:]) + if err != nil { + return err + } + // Craft an HTLC packet to send to the routing // sub-system. The meta-data within this packet will be // used to route the payment through the network. @@ -606,10 +606,11 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer) // TODO(roasbeef): semaphore to limit num outstanding // goroutines. go func() { - // Finally, send this next packet to the routing layer in order - // to complete the next payment. - // TODO(roasbeef): this should go through the L3 router once - // multi-hop is in place. + // Finally, send this next packet to the + // routing layer in order to complete the next + // payment. + // TODO(roasbeef): this should go through the + // L3 router once multi-hop is in place. if err := r.server.htlcSwitch.SendHTLC(htlcPkt); err != nil { errChan <- err return @@ -632,10 +633,11 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer) // the onion route specified by the passed list of graph vertexes. The blob // returned from this function can immediately be included within an HTLC add // packet to be sent to the first hop within the route. -func generateSphinxPacket(vertexes []graph.ID) ([]byte, error) { - var dest sphinx.LightningAddress - e2eMessage := []byte("test") - +func generateSphinxPacket(vertexes []graph.ID, paymentHash []byte) ([]byte, error) { + // First convert all the vertexs from the routing table to in-memory + // public key objects. These objects are necessary in order to perform + // the series of ECDH operations required to construct the Sphinx + // packet below. route := make([]*btcec.PublicKey, len(vertexes)) for i, vertex := range vertexes { vertexBytes, err := hex.DecodeString(vertex.String()) @@ -651,15 +653,35 @@ func generateSphinxPacket(vertexes []graph.ID) ([]byte, error) { route[i] = pub } - // Next generate the onion routing packet which allows - // us to perform privacy preserving source routing - // across the network. - var onionBlob bytes.Buffer - sphinxPacket, err := sphinx.NewOnionPacket(route, dest, - e2eMessage) + // Next we generate the per-hop payload which gives each node within + // the route the necessary information (fees, CLTV value, etc) to + // properly forward the payment. + // TODO(roasbeef): properly set CLTV value, payment amount, and chain + // within hop paylods. + var hopPayloads [][]byte + for i := 0; i < len(route); i++ { + payload := bytes.Repeat([]byte{byte('A' + i)}, + sphinx.HopPayloadSize) + hopPayloads = append(hopPayloads, payload) + } + + sessionKey, err := btcec.NewPrivateKey(btcec.S256()) if err != nil { return nil, err } + + // Next generate the onion routing packet which allows + // us to perform privacy preserving source routing + // across the network. + sphinxPacket, err := sphinx.NewOnionPacket(route, sessionKey, + hopPayloads, paymentHash) + if err != nil { + return nil, err + } + + // Finally, encode Sphinx packet using it's wire represenation to be + // included within the HTLC add packet. + var onionBlob bytes.Buffer if err := sphinxPacket.Encode(&onionBlob); err != nil { return nil, err }