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.
This commit is contained in:
Olaoluwa Osuntokun 2016-10-27 20:40:08 -07:00
parent ac43de94f6
commit f37956e38e
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2
5 changed files with 70 additions and 35 deletions

17
glide.lock generated

@ -1,5 +1,5 @@
hash: 0bdf51a0e40c8cba475333645a769b711e699be36803b02f3b14430642ab79b2 hash: 2106ce14ff53c14d3d0d3d8f34e1cf01c01a79eef409ffe871cd5783b77939c8
updated: 2016-10-17T19:44:40.047044856-07:00 updated: 2016-10-27T20:34:19.347013604-07:00
imports: imports:
- name: github.com/aead/chacha20 - name: github.com/aead/chacha20
version: 7e1038a97ad08a9a16cb88ed7a6778b366ba4d99 version: 7e1038a97ad08a9a16cb88ed7a6778b366ba4d99
@ -61,6 +61,8 @@ imports:
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
subpackages: subpackages:
- spew - spew
- name: github.com/go-errors/errors
version: a41850380601eeb43f4350f7d17c6bbd8944aaf8
- name: github.com/golang/protobuf - name: github.com/golang/protobuf
version: 98fa357170587e470c5f27d3c3ea0947b71eb455 version: 98fa357170587e470c5f27d3c3ea0947b71eb455
subpackages: subpackages:
@ -77,7 +79,7 @@ imports:
- name: github.com/howeyc/gopass - name: github.com/howeyc/gopass
version: f5387c492211eb133053880d23dfae62aa14123d version: f5387c492211eb133053880d23dfae62aa14123d
- name: github.com/lightningnetwork/lightning-onion - name: github.com/lightningnetwork/lightning-onion
version: 81647ffa2c5e17c0447d359e1963a54e18be85c4 version: f38a054899049d1f5bdb6550c17724060384161b
- name: github.com/roasbeef/btcd - name: github.com/roasbeef/btcd
version: baea7691cc3c59480703fe1a3fb5595c838c963c version: baea7691cc3c59480703fe1a3fb5595c838c963c
subpackages: subpackages:
@ -120,7 +122,7 @@ imports:
- name: github.com/urfave/cli - name: github.com/urfave/cli
version: a14d7d367bc02b1f57d88de97926727f2d936387 version: a14d7d367bc02b1f57d88de97926727f2d936387
- name: golang.org/x/crypto - name: golang.org/x/crypto
version: 5f31782cfb2b6373211f8f9fbf31283fa234b570 version: ca7e7f10cb9fd9c1a6ff7f60436c086d73714180
subpackages: subpackages:
- hkdf - hkdf
- nacl/secretbox - nacl/secretbox
@ -131,7 +133,7 @@ imports:
- pbkdf2 - pbkdf2
- ssh/terminal - ssh/terminal
- name: golang.org/x/net - name: golang.org/x/net
version: 8b4af36cd21a1f85a7484b49feb7c79363106d8e version: b336a971b799939dd16ae9b1df8334cb8b977c4d
subpackages: subpackages:
- context - context
- http2 - http2
@ -141,7 +143,7 @@ imports:
- lex/httplex - lex/httplex
- internal/timeseries - internal/timeseries
- name: golang.org/x/sys - name: golang.org/x/sys
version: 9bb9f0998d48b31547d975974935ae9b48c7a03c version: c200b10b5d5e122be351b67af224adc6128af5bf
subpackages: subpackages:
- unix - unix
- name: google.golang.org/grpc - name: google.golang.org/grpc
@ -155,7 +157,4 @@ imports:
- naming - naming
- transport - transport
- peer - peer
- name: github.com/go-errors/errors
version: a41850380601eeb43f4350f7d17c6bbd8944aaf8
testImports: [] testImports: []

@ -57,6 +57,7 @@ import:
- package: google.golang.org/grpc - package: google.golang.org/grpc
version: ^1.0.0 version: ^1.0.0
- package: github.com/lightningnetwork/lightning-onion - package: github.com/lightningnetwork/lightning-onion
version: master
- package: github.com/grpc-ecosystem/grpc-gateway - package: github.com/grpc-ecosystem/grpc-gateway
version: ^1.1.0 version: ^1.1.0
- package: github.com/aead/chacha20 - package: github.com/aead/chacha20

@ -57,6 +57,7 @@ type HTLCAddRequest struct {
// and the shared secret is fresh, then the node should stip off a layer // 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 // of encryption, exposing the next hop to be used in the subsequent
// HTLCAddRequest message. // HTLCAddRequest message.
// TODO(roasbeef): can be fixed sized now that v1 Sphinx is "done".
OnionBlob []byte OnionBlob []byte
} }

16
peer.go

@ -1109,13 +1109,24 @@ func (p *peer) handleUpstreamMsg(state *commitmentState, msg lnwire.Message) {
p.Disconnect() p.Disconnect()
return 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 { if err != nil {
peerLog.Errorf("unable to process onion pkt: %v", err) peerLog.Errorf("unable to process onion pkt: %v", err)
p.Disconnect() p.Disconnect()
return 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 // We just received an add request from an upstream peer, so we
// add it to our state machine, then add the HTLC to our // add it to our state machine, then add the HTLC to our
// "settle" list in the event that we know the pre-image // "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 // switch, we'll attach the routing information so the switch
// can finalize the circuit. // can finalize the circuit.
case sphinx.MoreHops: 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 state.pendingCircuits[index] = sphinxPacket
default: default:
peerLog.Errorf("mal formed onion packet") peerLog.Errorf("mal formed onion packet")

@ -564,16 +564,6 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
} }
rpcsLog.Tracef("[sendpayment] selected route: %v", path) 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 // If we're in debug HTLC mode, then all outgoing
// HTLC's will pay to the same debug rHash. Otherwise, // HTLC's will pay to the same debug rHash. Otherwise,
// we pay to the rHash specified within the RPC // 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) 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 // Craft an HTLC packet to send to the routing
// sub-system. The meta-data within this packet will be // sub-system. The meta-data within this packet will be
// used to route the payment through the network. // 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 // TODO(roasbeef): semaphore to limit num outstanding
// goroutines. // goroutines.
go func() { go func() {
// Finally, send this next packet to the routing layer in order // Finally, send this next packet to the
// to complete the next payment. // routing layer in order to complete the next
// TODO(roasbeef): this should go through the L3 router once // payment.
// multi-hop is in place. // TODO(roasbeef): this should go through the
// L3 router once multi-hop is in place.
if err := r.server.htlcSwitch.SendHTLC(htlcPkt); err != nil { if err := r.server.htlcSwitch.SendHTLC(htlcPkt); err != nil {
errChan <- err errChan <- err
return 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 // 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 // returned from this function can immediately be included within an HTLC add
// packet to be sent to the first hop within the route. // packet to be sent to the first hop within the route.
func generateSphinxPacket(vertexes []graph.ID) ([]byte, error) { func generateSphinxPacket(vertexes []graph.ID, paymentHash []byte) ([]byte, error) {
var dest sphinx.LightningAddress // First convert all the vertexs from the routing table to in-memory
e2eMessage := []byte("test") // 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)) route := make([]*btcec.PublicKey, len(vertexes))
for i, vertex := range vertexes { for i, vertex := range vertexes {
vertexBytes, err := hex.DecodeString(vertex.String()) vertexBytes, err := hex.DecodeString(vertex.String())
@ -651,15 +653,35 @@ func generateSphinxPacket(vertexes []graph.ID) ([]byte, error) {
route[i] = pub route[i] = pub
} }
// Next generate the onion routing packet which allows // Next we generate the per-hop payload which gives each node within
// us to perform privacy preserving source routing // the route the necessary information (fees, CLTV value, etc) to
// across the network. // properly forward the payment.
var onionBlob bytes.Buffer // TODO(roasbeef): properly set CLTV value, payment amount, and chain
sphinxPacket, err := sphinx.NewOnionPacket(route, dest, // within hop paylods.
e2eMessage) 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 { if err != nil {
return nil, err 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 { if err := sphinxPacket.Encode(&onionBlob); err != nil {
return nil, err return nil, err
} }