diff --git a/htlcswitch/iterator.go b/htlcswitch/iterator.go index 9387df6f..f7d65db0 100644 --- a/htlcswitch/iterator.go +++ b/htlcswitch/iterator.go @@ -6,6 +6,10 @@ import ( "io" "github.com/btcsuite/golangcrypto/ripemd160" + "github.com/go-errors/errors" + "github.com/lightningnetwork/lightning-onion" + "github.com/lightningnetwork/lnd/routing" + "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcutil" ) @@ -41,3 +45,143 @@ type HopIterator interface { // Encode encodes iterator and writes it to the writer. Encode(w io.Writer) error } + +// sphinxHopIterator is the Sphinx implementation of hop iterator which uses +// onion routing to encode route in such a way so that node might see only the +// next hop in the route, after retrieving hop iterator will behave as if +// there is no hop in path. +type sphinxHopIterator struct { + onionPacket *sphinx.OnionPacket + sphinxPacket *sphinx.ProcessedPacket +} + +// // A compile time check to ensure sphinxHopIterator implements the HopIterator +// interface. +var _ HopIterator = (*sphinxHopIterator)(nil) + +// NewSphinxBlob creates new instance of sphinx hop iterator. +func NewSphinxBlob(route *routing.Route, paymentHash []byte) ([]byte, error) { + + // First obtain all the public keys along the route which are contained + // in each hop. + nodes := make([]*btcec.PublicKey, len(route.Hops)) + for i, hop := range route.Hops { + // We create a new instance of the public key to avoid possibly + // mutating the curve parameters, which are unset in a higher + // level in order to avoid spamming the logs. + pub := btcec.PublicKey{ + Curve: btcec.S256(), + X: hop.Channel.Node.PubKey.X, + Y: hop.Channel.Node.PubKey.Y, + } + nodes[i] = &pub + } + + // 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 payloads. + var hopPayloads [][]byte + for i := 0; i < len(route.Hops); 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. + onionPacket, err := sphinx.NewOnionPacket(nodes, sessionKey, + hopPayloads, paymentHash) + if err != nil { + return nil, err + } + + // Finally, encode Sphinx packet using it's wire representation to be + // included within the HTLC add packet. + var onionBlob bytes.Buffer + if err := onionPacket.Encode(&onionBlob); err != nil { + return nil, err + } + + return onionBlob.Bytes(), nil +} + +// Encode encodes iterator and writes it to the writer. +// NOTE: Part of the HopIterator interface. +func (r *sphinxHopIterator) Encode(w io.Writer) error { + return r.onionPacket.Encode(w) +} + +// Next returns next hop if exist and nil if route is ended. +// NOTE: Part of the HopIterator interface. +func (r *sphinxHopIterator) Next() *HopID { + // If next node was already given than behave as if no hops in route. + if r.sphinxPacket == nil { + return nil + } + + switch r.sphinxPacket.Action { + case sphinx.ExitNode: + return nil + + case sphinx.MoreHops: + id := (*HopID)(&r.sphinxPacket.NextHop) + r.sphinxPacket = nil + return id + } + + return nil +} + +// SphinxDecoder is responsible for keeping all sphinx dependent parts inside +// and expose only decoding function. With such approach we give freedom for +// subsystems which wants to decode sphinx path to not be dependable from sphinx +// at all. +// +// NOTE: The reason for keeping decoder separated from hop iterator is too +// maintain the hop iterator abstraction. Without it the structures which using +// the hop iterator should contain sphinx router which makes their +// creations in tests dependent from the sphinx internal parts. +type SphinxDecoder struct { + router *sphinx.Router +} + +// NewSphinxDecoder creates new instance of decoder. +func NewSphinxDecoder(router *sphinx.Router) *SphinxDecoder { + return &SphinxDecoder{router} +} + +// Decode takes byte stream as input and decodes the route/ hop iterator. +func (p *SphinxDecoder) Decode(r io.Reader, rHash []byte) (HopIterator, error) { + // Before adding the new HTLC to the state machine, parse the + // onion object in order to obtain the routing information. + onionPkt := &sphinx.OnionPacket{} + if err := onionPkt.Decode(r); err != nil { + return nil, errors.Errorf("unable to decode onion pkt: %v", + err) + + } + + // 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. + sphinxPacket, err := p.router.ProcessOnionPacket(onionPkt, rHash) + if err != nil { + return nil, errors.Errorf("unable to process onion pkt: "+ + "%v", err) + } + + return HopIterator(&sphinxHopIterator{ + onionPacket: sphinxPacket.Packet, + sphinxPacket: sphinxPacket, + }), nil +}