diff --git a/bench_test.go b/bench_test.go index cb345ad..ea4e86e 100644 --- a/bench_test.go +++ b/bench_test.go @@ -28,15 +28,19 @@ func BenchmarkPathPacketConstruction(b *testing.B) { } hopData := HopData{ - Realm: [1]byte{0x00}, ForwardAmount: uint64(i), OutgoingCltv: uint32(i), } copy(hopData.NextAddress[:], bytes.Repeat([]byte{byte(i)}, 8)) + hopPayload, err := NewHopPayload(&hopData, nil) + if err != nil { + b.Fatalf("unable to create new hop payload: %v", err) + } + route[i] = OnionHop{ - NodePub: *privKey.PubKey(), - HopData: hopData, + NodePub: *privKey.PubKey(), + HopPayload: hopPayload, } } diff --git a/cmd/main.go b/cmd/main.go index 8d1b413..183db99 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -3,6 +3,7 @@ package main import ( "bytes" "encoding/hex" + "encoding/json" "fmt" "io/ioutil" "log" @@ -14,6 +15,68 @@ import ( sphinx "github.com/lightningnetwork/lightning-onion" ) +type OnionHopSpec struct { + Realm int `json:"realm"` + PublicKey string `json:"pubkey"` + Payload string `json:"payload"` +} + +type OnionSpec struct { + SessionKey string `json:"session_key,omitempty"` + Hops []OnionHopSpec `json:"hops"` +} + +func parseOnionSpec(spec OnionSpec) (*sphinx.PaymentPath, *btcec.PrivateKey, error) { + var path sphinx.PaymentPath + var binSessionKey []byte + var err error + + if spec.SessionKey != "" { + binSessionKey, err = hex.DecodeString(spec.SessionKey) + if err != nil { + log.Fatalf("Unable to decode the sessionKey %v: %v\n", spec.SessionKey, err) + } + + if len(binSessionKey) != 32 { + log.Fatalf("Session key must be a 32 byte hex string: %v\n", spec.SessionKey) + } + } else { + binSessionKey = bytes.Repeat([]byte{'A'}, 32) + } + + sessionKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), binSessionKey) + + for i, hop := range spec.Hops { + binKey, err := hex.DecodeString(hop.PublicKey) + if err != nil || len(binKey) != 33 { + log.Fatalf("%s is not a valid hex pubkey %s", hop.PublicKey, err) + } + + pubkey, err := btcec.ParsePubKey(binKey, btcec.S256()) + if err != nil { + log.Fatalf("%s is not a valid hex pubkey %s", hop.PublicKey, err) + } + + path[i].NodePub = *pubkey + + payload, err := hex.DecodeString(hop.Payload) + if err != nil { + log.Fatalf("%s is not a valid hex payload %s", + hop.Payload, err) + } + + hopPayload, err := sphinx.NewHopPayload(nil, payload) + if err != nil { + log.Fatalf("unable to make payload: %v", err) + } + + path[i].HopPayload = hopPayload + + fmt.Fprintf(os.Stderr, "Node %d pubkey %x\n", i, pubkey.SerializeCompressed()) + } + return &path, sessionKey, nil +} + // main implements a simple command line utility that can be used in order to // either generate a fresh mix-header or decode and fully process an existing // one given a private key. @@ -22,44 +85,33 @@ func main() { assocData := bytes.Repeat([]byte{'B'}, 32) - if len(args) == 1 { - fmt.Printf("Usage: %s (generate|decode) \n", args[0]) + if len(args) < 3 { + fmt.Printf("Usage: %s (generate|decode) \n", args[0]) + return } else if args[1] == "generate" { - var path sphinx.PaymentPath - for i, hexKey := range args[2:] { - binKey, err := hex.DecodeString(hexKey) - if err != nil || len(binKey) != 33 { - log.Fatalf("%s is not a valid hex pubkey %s", hexKey, err) - } - - pubkey, err := btcec.ParsePubKey(binKey, btcec.S256()) - if err != nil { - panic(err) - } - - path[i] = sphinx.OnionHop{ - NodePub: *pubkey, - HopData: sphinx.HopData{ - Realm: [1]byte{0x00}, - ForwardAmount: uint64(i), - OutgoingCltv: uint32(i), - }, - } - copy(path[i].HopData.NextAddress[:], bytes.Repeat([]byte{byte(i)}, 8)) - - fmt.Fprintf(os.Stderr, "Node %d pubkey %x\n", i, pubkey.SerializeCompressed()) - } - - sessionKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), bytes.Repeat([]byte{'A'}, 32)) - - msg, err := sphinx.NewOnionPacket(&path, sessionKey, assocData) + var spec OnionSpec + + jsonSpec, err := ioutil.ReadFile(args[2]) + if err != nil { + log.Fatalf("Unable to read JSON onion spec from file %v: %v", args[2], err) + } + + if err := json.Unmarshal(jsonSpec, &spec); err != nil { + log.Fatalf("Unable to parse JSON onion spec: %v", err) + } + + path, sessionKey, err := parseOnionSpec(spec) + if err != nil { + log.Fatalf("could not parse onion spec: %v", err) + } + + msg, err := sphinx.NewOnionPacket(path, sessionKey, assocData) if err != nil { log.Fatalf("Error creating message: %v", err) } w := bytes.NewBuffer([]byte{}) err = msg.Encode(w) - if err != nil { log.Fatalf("Error serializing message: %v", err) } @@ -78,8 +130,11 @@ func main() { } privkey, _ := btcec.PrivKeyFromBytes(btcec.S256(), binKey) - s := sphinx.NewRouter(privkey, &chaincfg.TestNet3Params, - sphinx.NewMemoryReplayLog()) + replayLog := sphinx.NewMemoryReplayLog() + s := sphinx.NewRouter(privkey, &chaincfg.TestNet3Params, replayLog) + + replayLog.Start() + defer replayLog.Stop() var packet sphinx.OnionPacket err = packet.Decode(bytes.NewBuffer(binMsg)) diff --git a/go.mod b/go.mod index 67d14e7..72d41f8 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,10 @@ module github.com/lightningnetwork/lightning-onion require ( github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da - github.com/btcsuite/btcd v0.0.0-20181130015935-7d2daa5bfef2 + github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8 github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f - github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a - github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495 - golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc - golang.org/x/sys v0.0.0-20190102155601-82a175fd1598 // indirect + github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d + github.com/davecgh/go-spew v1.1.1 + github.com/lightningnetwork/lnd v0.7.1-beta.0.20190807225126-ea77ff91c221 + golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 ) diff --git a/go.sum b/go.sum index 256e849..3412e80 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,230 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e h1:F2x1bq7RaNCIuqYpswggh1+c1JmwdnkHNC9wy1KDip0= +git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo= +github.com/NebulousLabs/fastrand v0.0.0-20180208210444-3cf7173006a0 h1:g/ETZwHx5wN2fqKWS3gCUrEU7dLko+DvVs3hakQCfyE= +github.com/NebulousLabs/fastrand v0.0.0-20180208210444-3cf7173006a0/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ= +github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 h1:MG93+PZYs9PyEsj/n5/haQu2gK0h4tUtSy9ejtMwWa0= +github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2 h1:2be4ykKKov3M1yISM2E8gnGXZ/N2SsPawfnGiXxaYEU= +github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= +github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/btcsuite/btcd v0.0.0-20181130015935-7d2daa5bfef2 h1:LPHpTTuR7vj3kD7YDRZnrDnFAoj1Ov4cpiO3jN8RnW4= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/btcsuite/btcd v0.0.0-20180823030728-d81d8877b8f3/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.0.0-20181130015935-7d2daa5bfef2/go.mod h1:Jr9bmNVGZ7TH2Ux1QuP0ec+yGgh0gE9FIlkzQiI5bR0= +github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= +github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.0.0-20190605094302-a0d1e3e36d50/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8 h1:mOg8/RgDSHTQ1R0IR+LMDuW4TDShPv+JzYHuR4GLoNA= +github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a h1:RQMUrEILyYJEoAT34XS/kLu40vC0+po/UfxrBBA4qZE= github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcwallet v0.0.0-20180904010540-284e2e0e696e33d5be388f7f3d9a26db703e0c06/go.mod h1:/d7QHZsfUAruXuBhyPITqoYOmJ+nq35qPsJjz/aSpCg= +github.com/btcsuite/btcwallet v0.0.0-20190313032608-acf3b04b0273/go.mod h1:mkOYY8/psBiL5E+Wb0V7M0o+N7NXi2SZJz6+RKkncIc= +github.com/btcsuite/btcwallet v0.0.0-20190319010515-89ab2044f962/go.mod h1:qMi4jGpAO6YRsd81RYDG7o5pBIGqN9faCioJdagLu64= +github.com/btcsuite/btcwallet v0.0.0-20190712034938-7a3a3e82cbb6 h1:xzQam31gpeJrFpxntJ3/OnY3UxyDdTZw0wKqvFCFA3A= +github.com/btcsuite/btcwallet v0.0.0-20190712034938-7a3a3e82cbb6/go.mod h1:sXVxjjP5YeWqWsiQbQDXvAw6J6Qvr8swu7MONoNaF9k= +github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941 h1:kij1x2aL7VE6gtx8KMIt8PGPgI5GV9LgtHFG5KaEMPY= +github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941/go.mod h1:QcFA8DZHtuIAdYKCq/BzELOaznRsCvwf4zTPmaYwaig= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8 h1:nOsAWScwueMVk/VLm/dvQQD7DuanyvAUb6B3P3eT274= +github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0 h1:Tvd0BfvqX9o823q1j2UZ/epQo09eJh6dTcRp79ilIN4= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJGQE= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v0.0.0-20180223184059-7ee3ded59d4835e10f3e7d0f7603c42aa5e83820/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= +github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495 h1:6IyqGr3fnd0tM3YxipK27TUskaOVUjU2nG45yzwcQKY= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v0.0.0-20180821051752-b27b920f9e71/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v0.0.0-20170724004829-f2862b476edc h1:3NXdOHZ1YlN6SGP3FPbn4k73O2MeEp065abehRwGFxI= +github.com/grpc-ecosystem/grpc-gateway v0.0.0-20170724004829-f2862b476edc/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc= +github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= +github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad h1:heFfj7z0pGsNCekUlsFhO2jstxO4b5iQ665LjwM5mDc= +github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/juju/clock v0.0.0-20180808021310-bab88fc67299/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= +github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20180524022052-584905176618 h1:MK144iBQF9hTSwBW/9eJm034bVoG30IshVm688T2hi8= +github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= +github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= +github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFFoO5dR748Is8dBL9qpaTNfphQrs= +github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lightninglabs/gozmq v0.0.0-20180324010646-462a8a753885/go.mod h1:KUh15naRlx/TmUMFS/p4JJrCrE6F7RGF7rsnvuu45E4= +github.com/lightninglabs/gozmq v0.0.0-20190710231225-cea2a031735d h1:tt8hwvxl6fksSfchjBGaWu+pnWJQfG1OWiCM20qOSAE= +github.com/lightninglabs/gozmq v0.0.0-20190710231225-cea2a031735d/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= +github.com/lightninglabs/neutrino v0.0.0-20181017011010-4d6069299130/go.mod h1:KJq43Fu9ceitbJsSXMILcT4mGDNI/crKmPIkDOZXFyM= +github.com/lightninglabs/neutrino v0.0.0-20190213031021-ae4583a89cfb/go.mod h1:g6cMQd+hfAU8pQTJAdjm6/EQREhupyd22f+CL0qYFOE= +github.com/lightninglabs/neutrino v0.0.0-20190313035638-e1ad4c33fb18/go.mod h1:v6tz6jbuAubTrRpX8ke2KH9sJxml8KlPQTKgo9mAp1Q= +github.com/lightninglabs/neutrino v0.0.0-20190725230401-ddf667a8b5c4 h1:Yq3usMeTtJyRHFRJQsVqmr5oJTFm6uhZdKL2/YhoVrA= +github.com/lightninglabs/neutrino v0.0.0-20190725230401-ddf667a8b5c4/go.mod h1:vzLU75ll8qbRJIzW5dvK/UXtR9c2FecJ6VNOM8chyVM= +github.com/lightningnetwork/lightning-onion v0.0.0-20190703000913-ecc936dc56c9/go.mod h1:Sooe/CoCqa85JxqHV+IBR2HW+6t2Cv+36awSmoccswM= +github.com/lightningnetwork/lnd v0.7.1-beta.0.20190807225126-ea77ff91c221 h1:05liAcL0IpCWdKYTEhKJyAOnzROMhj90ZG52YWzItQU= +github.com/lightningnetwork/lnd v0.7.1-beta.0.20190807225126-ea77ff91c221/go.mod h1:F6I/YZmW3e+kVRgQtCLqvLzBFl+tRCyoT8nolFeqQOo= +github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= +github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= +github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU= +github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0= +github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw= +github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY= +github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8 h1:PRMAcldsl4mXKJeRNB/KVNz6TlbS6hk2Rs42PqgU3Ws= +github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sKxUCEXRoeCvVG90TJmwhiqRpvdhQFng= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E= +github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY= +github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +go.etcd.io/bbolt v1.3.0/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc h1:F5tKCVGp+MUAHhKp5MZtGqAlGX3+oCsiL1Q629FL90M= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/sys v0.0.0-20190102155601-82a175fd1598 h1:S8GOgffXV1X3fpVG442QRfWOt0iFl79eHJ7OPt725bo= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180821023952-922f4815f713/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190206173232-65e2d4e15006 h1:bfLnR+k0tq5Lqt6dflRLcZiz6UaXCMt3vhYJ1l4FQ80= +golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180821140842-3b58ed4ad339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503 h1:5SvYFrOM3W8Mexn9/oA44Ji7vhXAZQ9hiP+1Q/DMrWg= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922 h1:mBVYJnbrXLA/ZCBTCe7PtEgAUP+1bg92qTaFoPHdz+8= +google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.18.0 h1:IZl7mfBGfbhYx2p2rKRtYgDFw6SBz+kclmxYrCksPPA= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v1 v1.0.0 h1:n+7XfCyygBFb8sEjg6692xjC6Us50TFRO54+xYUEwjE= +gopkg.in/errgo.v1 v1.0.0/go.mod h1:CxwszS/Xz1C49Ucd2i6Zil5UToP1EmyrFhKaMVbg1mk= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/macaroon-bakery.v2 v2.0.1 h1:0N1TlEdfLP4HXNCg7MQUMp5XwvOoxk+oe9Owr2cpvsc= +gopkg.in/macaroon-bakery.v2 v2.0.1/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETfr3S9MbIA= +gopkg.in/macaroon.v2 v2.0.0 h1:LVWycAfeJBUjCIqfR9gqlo7I8vmiXRr51YEOZ1suop8= +gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/path.go b/path.go index 3d41886..5ff51f9 100644 --- a/path.go +++ b/path.go @@ -1,9 +1,314 @@ package sphinx -import "github.com/btcsuite/btcd/btcec" +import ( + "bufio" + "bytes" + "encoding/binary" + "fmt" + "io" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/tlv" +) + +// HopData is the information destined for individual hops. It is a fixed size +// 64 bytes, prefixed with a 1 byte realm that indicates how to interpret it. +// For now we simply assume it's the bitcoin realm (0x00) and hence the format +// is fixed. The last 32 bytes are always the HMAC to be passed to the next +// hop, or zero if this is the packet is not to be forwarded, since this is the +// last hop. +type HopData struct { + // Realm denotes the "real" of target chain of the next hop. For + // bitcoin, this value will be 0x00. + Realm [RealmByteSize]byte + + // NextAddress is the address of the next hop that this packet should + // be forward to. + NextAddress [AddressSize]byte + + // ForwardAmount is the HTLC amount that the next hop should forward. + // This value should take into account the fee require by this + // particular hop, and the cumulative fee for the entire route. + ForwardAmount uint64 + + // OutgoingCltv is the value of the outgoing absolute time-lock that + // should be included in the HTLC forwarded. + OutgoingCltv uint32 + + // ExtraBytes is the set of unused bytes within the onion payload. This + // extra set of bytes can be utilized by higher level applications to + // package additional data within the per-hop payload, or signal that a + // portion of the remaining set of hops are to be consumed as Extra + // Onion Blobs. + // + // TODO(roasbeef): rename to padding bytes? + ExtraBytes [NumPaddingBytes]byte +} + +// Encode writes the serialized version of the target HopData into the passed +// io.Writer. +func (hd *HopData) Encode(w io.Writer) error { + if _, err := w.Write(hd.Realm[:]); err != nil { + return err + } + + if _, err := w.Write(hd.NextAddress[:]); err != nil { + return err + } + + if err := binary.Write(w, binary.BigEndian, hd.ForwardAmount); err != nil { + return err + } + + if err := binary.Write(w, binary.BigEndian, hd.OutgoingCltv); err != nil { + return err + } + + if _, err := w.Write(hd.ExtraBytes[:]); err != nil { + return err + } + + return nil +} + +// Decodes populates the target HopData with the contents of a serialized +// HopData packed into the passed io.Reader. +func (hd *HopData) Decode(r io.Reader) error { + if _, err := io.ReadFull(r, hd.Realm[:]); err != nil { + return err + } + + if _, err := io.ReadFull(r, hd.NextAddress[:]); err != nil { + return err + } + + err := binary.Read(r, binary.BigEndian, &hd.ForwardAmount) + if err != nil { + return err + } + + err = binary.Read(r, binary.BigEndian, &hd.OutgoingCltv) + if err != nil { + return err + } + + _, err = io.ReadFull(r, hd.ExtraBytes[:]) + return err +} + +// PayloadType denotes the type of the payload included in the onion packet. +// Serialization of a raw HopPayload will depend on the payload type, as some +// include a varint length prefix, while others just encode the raw payload. +type PayloadType uint8 + +const ( + // PayloadLegacy is the legacy payload type. It includes a fixed 32 + // bytes, 12 of which are padding, and uses a "zero length" (the old + // realm) prefix. + PayloadLegacy PayloadType = iota + + // PayloadTLV is the new modern TLV based format. This payload includes + // a set of opaque bytes with a varint length prefix. The varint used + // is the same CompactInt as used in the Bitcoin protocol. + PayloadTLV +) + +// HopPayload is a slice of bytes and associated payload-type that are destined +// for a specific hop in the PaymentPath. The payload itself is treated as an +// opaque data field by the onion router. The included Type field informs the +// serialization/deserialziation of the raw payload. +type HopPayload struct { + // Type is the type of the payload. + Type PayloadType + + // Payload is the raw bytes of the per-hop payload for this hop. + // Depending on the realm, this pay be the regular legacy hop data, or + // a set of opaque blobs to be parsed by higher layers. + Payload []byte + + // HMAC is an HMAC computed over the entire per-hop payload that also + // includes the higher-level (optional) associated data bytes. + HMAC [HMACSize]byte +} + +// NewHopPayload creates a new hop payload given an optional set of forwarding +// instructions for a hop, and a set of optional opaque extra onion bytes to +// drop off at the target hop. If both values are not specified, then an error +// is returned. +func NewHopPayload(hopData *HopData, eob []byte) (HopPayload, error) { + var ( + h HopPayload + b bytes.Buffer + ) + + // We can't proceed if neither the hop data or the EOB has been + // specified by the caller. + switch { + case hopData == nil && len(eob) == 0: + return h, fmt.Errorf("either hop data or eob must " + + "be specified") + + case hopData != nil && len(eob) > 0: + return h, fmt.Errorf("cannot provide both hop data AND an eob") + + } + + // If the hop data is specified, then we'll write that now, as it + // should proceed the EOB portion of the payload. + if hopData != nil { + if err := hopData.Encode(&b); err != nil { + return h, nil + } + + // We'll also mark that this particular hop will be using the + // legacy format as the modern format packs the existing hop + // data information into the EOB space as a TLV stream. + h.Type = PayloadLegacy + } else { + // Otherwise, we'll write out the raw EOB which contains a set + // of opaque bytes that the recipient can decode to make a + // forwarding decision. + if _, err := b.Write(eob); err != nil { + return h, nil + } + + h.Type = PayloadTLV + } + + h.Payload = b.Bytes() + + return h, nil +} + +// NumBytes returns the number of bytes it will take to serialize the full +// payload. Depending on the payload type, this may include some additional +// signalling bytes. +func (hp *HopPayload) NumBytes() int { + // The base size is the size of the raw payload, and the size of the + // HMAC. + size := len(hp.Payload) + HMACSize + + // If this is the new TLV format, then we'll also accumulate the number + // of bytes that it would take to encode the size of the payload. + if hp.Type == PayloadTLV { + payloadSize := len(hp.Payload) + size += int(wire.VarIntSerializeSize(uint64(payloadSize))) + } + + return size +} + +// Encode encodes the hop payload into the passed writer. +func (hp *HopPayload) Encode(w io.Writer) error { + switch hp.Type { + + // For the legacy payload, we don't need to add any additional bytes as + // our realm byte serves as our zero prefix byte. + case PayloadLegacy: + break + + // For the TLV payload, we'll first prepend the length of the payload + // as a var-int. + case PayloadTLV: + var b [8]byte + err := tlv.WriteVarInt(w, uint64(len(hp.Payload)), &b) + if err != nil { + return err + } + } + + // Finally, we'll write out the raw payload, then the HMAC in series. + if _, err := w.Write(hp.Payload); err != nil { + return err + } + if _, err := w.Write(hp.HMAC[:]); err != nil { + return err + } + + return nil +} + +// Decode unpacks an encoded HopPayload from the passed reader into the target +// HopPayload. +func (hp *HopPayload) Decode(r io.Reader) error { + bufReader := bufio.NewReader(r) + + // In order to properly parse the payload, we'll need to check the + // first byte. We'll use a bufio reader to peek at it without consuming + // it from the buffer. + peekByte, err := bufReader.Peek(1) + if err != nil { + return err + } + + var payloadSize uint32 + + switch int(peekByte[0]) { + // If the first byte is a zero (the realm), then this is the normal + // payload. + case 0x00: + // Our size is just the payload, without the HMAC. This means + // that this is the legacy payload type. + payloadSize = HopDataSize - HMACSize + hp.Type = PayloadLegacy + + default: + // Otherwise, this is the new TLV based payload type, so we'll + // extract the payload length encoded as a var-int. + var b [8]byte + varInt, err := tlv.ReadVarInt(bufReader, &b) + if err != nil { + return err + } + + payloadSize = uint32(varInt) + hp.Type = PayloadTLV + } + + // Now that we know the payload size, we'll create a new buffer to + // read it out in full. + // + // TODO(roasbeef): can avoid all these copies + hp.Payload = make([]byte, payloadSize) + if _, err := io.ReadFull(bufReader, hp.Payload[:]); err != nil { + return err + } + if _, err := io.ReadFull(bufReader, hp.HMAC[:]); err != nil { + return err + } + + return nil +} + +// HopData attempts to extract a set of forwarding instructions from the target +// HopPayload. If the realm isn't what we expect, then an error is returned. +// This method also returns the left over EOB that remain after the hop data +// has been parsed. Callers may want to map this blob into something more +// concrete. +func (hp *HopPayload) HopData() (*HopData, error) { + payloadReader := bytes.NewBuffer(hp.Payload) + + // If this isn't the "base" realm, then we can't extract the expected + // hop payload structure from the payload. + if hp.Type != PayloadLegacy { + return nil, nil + } + + // Now that we know the payload has the structure we expect, we'll + // decode the payload into the HopData. + var hd HopData + if err := hd.Decode(payloadReader); err != nil { + return nil, err + } + + return &hd, nil +} // NumMaxHops is the maximum path length. This should be set to an estimate of // the upper limit of the diameter of the node graph. +// +// TODO(roasbeef): adjust due to var-payloads? const NumMaxHops = 20 // PaymentPath represents a series of hops within the Lightning Network @@ -28,9 +333,9 @@ type OnionHop struct { // internal packet to the next hop. NodePub btcec.PublicKey - // HopData are the plaintext routing instructions that should be - // delivered to this hop. - HopData HopData + // HopPayload is the opaque payload provided to this node. If the + // HopData above is specified, then it'll be packed into this payload. + HopPayload HopPayload } // IsEmpty returns true if the hop isn't populated. @@ -70,3 +375,18 @@ func (p *PaymentPath) TrueRouteLength() int { return routeLength } + +// TotalPayloadSize returns the sum of the size of each payload in the "true" +// route. +func (p *PaymentPath) TotalPayloadSize() int { + var totalSize int + for _, hop := range p { + if hop.IsEmpty() { + continue + } + + totalSize += hop.HopPayload.NumBytes() + } + + return totalSize +} diff --git a/sphinx.go b/sphinx.go index b10100a..b01c404 100644 --- a/sphinx.go +++ b/sphinx.go @@ -5,7 +5,6 @@ import ( "crypto/ecdsa" "crypto/hmac" "crypto/sha256" - "encoding/binary" "io" "math/big" @@ -44,6 +43,13 @@ const ( HopDataSize = (RealmByteSize + AddressSize + AmtForwardSize + OutgoingCLTVSize + NumPaddingBytes + HMACSize) + // MaxPayloadSize is the maximum size a payload for a single hop can + // be. This is the worst case scenario of a single hop, consuming all + // 20 frames. We need to know this in order to generate a sufficiently + // long stream of pseudo-random bytes when encrypting/decrypting the + // payload. + MaxPayloadSize = NumMaxHops * HopDataSize + // sharedSecretSize is the size in bytes of the shared secrets. sharedSecretSize = 32 @@ -56,9 +62,10 @@ const ( // numStreamBytes is the number of bytes produced by our CSPRG for the // key stream implementing our stream cipher to encrypt/decrypt the mix - // header. The last hopDataSize bytes are only used in order to - // generate/check the MAC over the header. - numStreamBytes = routingInfoSize + HopDataSize + // header. The MaxPayloadSize bytes at the end are used to + // encrypt/decrypt the fillers when processing the packet of generating + // the HMACs when creating the packet. + numStreamBytes = routingInfoSize * 2 // keyLen is the length of the keys used to generate cipher streams and // encrypt payloads. Since we use SHA256 to generate the keys, the @@ -104,104 +111,6 @@ type OnionPacket struct { HeaderMAC [HMACSize]byte } -// HopData is the information destined for individual hops. It is a fixed size -// 64 bytes, prefixed with a 1 byte realm that indicates how to interpret it. -// For now we simply assume it's the bitcoin realm (0x00) and hence the format -// is fixed. The last 32 bytes are always the HMAC to be passed to the next -// hop, or zero if this is the packet is not to be forwarded, since this is the -// last hop. -type HopData struct { - // Realm denotes the "real" of target chain of the next hop. For - // bitcoin, this value will be 0x00. - Realm [RealmByteSize]byte - - // NextAddress is the address of the next hop that this packet should - // be forward to. - NextAddress [AddressSize]byte - - // ForwardAmount is the HTLC amount that the next hop should forward. - // This value should take into account the fee require by this - // particular hop, and the cumulative fee for the entire route. - ForwardAmount uint64 - - // OutgoingCltv is the value of the outgoing absolute time-lock that - // should be included in the HTLC forwarded. - OutgoingCltv uint32 - - // ExtraBytes is the set of unused bytes within the onion payload. This - // extra set of bytes can be utilized by higher level applications to - // package additional data within the per-hop payload, or signal that a - // portion of the remaining set of hops are to be consumed as Extra - // Onion Blobs. - // - // TODO(roasbeef): rename to padding bytes? - ExtraBytes [NumPaddingBytes]byte - - // HMAC is an HMAC computed over the entire per-hop payload that also - // includes the higher-level (optional) associated data bytes. - HMAC [HMACSize]byte -} - -// Encode writes the serialized version of the target HopData into the passed -// io.Writer. -func (hd *HopData) Encode(w io.Writer) error { - if _, err := w.Write(hd.Realm[:]); err != nil { - return err - } - - if _, err := w.Write(hd.NextAddress[:]); err != nil { - return err - } - - if err := binary.Write(w, binary.BigEndian, hd.ForwardAmount); err != nil { - return err - } - - if err := binary.Write(w, binary.BigEndian, hd.OutgoingCltv); err != nil { - return err - } - - if _, err := w.Write(hd.ExtraBytes[:]); err != nil { - return err - } - - if _, err := w.Write(hd.HMAC[:]); err != nil { - return err - } - - return nil -} - -// Decode deserializes the encoded HopData contained int he passed io.Reader -// instance to the target empty HopData instance. -func (hd *HopData) Decode(r io.Reader) error { - if _, err := io.ReadFull(r, hd.Realm[:]); err != nil { - return err - } - - if _, err := io.ReadFull(r, hd.NextAddress[:]); err != nil { - return err - } - - if err := binary.Read(r, binary.BigEndian, &hd.ForwardAmount); err != nil { - return err - } - - if err := binary.Read(r, binary.BigEndian, &hd.OutgoingCltv); err != nil { - return err - } - - if _, err := io.ReadFull(r, hd.ExtraBytes[:]); err != nil { - return err - } - - if _, err := io.ReadFull(r, hd.HMAC[:]); err != nil { - return err - } - - return nil -} - // generateSharedSecrets by the given nodes pubkeys, generates the shared // secrets. func generateSharedSecrets(paymentPath []*btcec.PublicKey, @@ -287,16 +196,14 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, ) // Generate the padding, called "filler strings" in the paper. - filler := generateHeaderPadding( - "rho", numHops, HopDataSize, hopSharedSecrets, - ) + filler := generateHeaderPadding("rho", paymentPath, hopSharedSecrets) // Allocate zero'd out byte slices to store the final mix header packet // and the hmac for each hop. var ( - mixHeader [routingInfoSize]byte - nextHmac [HMACSize]byte - hopDataBuf bytes.Buffer + mixHeader [routingInfoSize]byte + nextHmac [HMACSize]byte + hopPayloadBuf bytes.Buffer ) // Now we compute the routing information for each hop, along with a @@ -311,31 +218,32 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, // The HMAC for the final hop is simply zeroes. This allows the // last hop to recognize that it is the destination for a // particular payment. - paymentPath[i].HopData.HMAC = nextHmac + paymentPath[i].HopPayload.HMAC = nextHmac // Next, using the key dedicated for our stream cipher, we'll // generate enough bytes to obfuscate this layer of the onion // packet. - streamBytes := generateCipherStream(rhoKey, numStreamBytes) + streamBytes := generateCipherStream(rhoKey, routingInfoSize) + + payload := paymentPath[i].HopPayload // Before we assemble the packet, we'll shift the current - // mix-header to the write in order to make room for this next + // mix-header to the right in order to make room for this next // per-hop data. - rightShift(mixHeader[:], HopDataSize) + shiftSize := payload.NumBytes() + rightShift(mixHeader[:], shiftSize) - // With the mix header right-shifted, we'll encode the current - // hop data into a buffer we'll re-use during the packet - // construction. - err := paymentPath[i].HopData.Encode(&hopDataBuf) + err := payload.Encode(&hopPayloadBuf) if err != nil { return nil, err } - copy(mixHeader[:], hopDataBuf.Bytes()) + + copy(mixHeader[:], hopPayloadBuf.Bytes()) // Once the packet for this hop has been assembled, we'll // re-encrypt the packet by XOR'ing with a stream of bytes // generated using our shared secret. - xor(mixHeader[:], mixHeader[:], streamBytes[:routingInfoSize]) + xor(mixHeader[:], mixHeader[:], streamBytes[:]) // If this is the "last" hop, then we'll override the tail of // the hop data. @@ -350,7 +258,7 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, packet := append(mixHeader[:], assocData...) nextHmac = calcMac(muKey, packet) - hopDataBuf.Reset() + hopPayloadBuf.Reset() } return &OnionPacket{ @@ -375,26 +283,41 @@ func rightShift(slice []byte, num int) { // generateHeaderPadding derives the bytes for padding the mix header to ensure // it remains fixed sized throughout route transit. At each step, we add -// 'hopSize' padding of zeroes, concatenate it to the previous filler, then -// decrypt it (XOR) with the secret key of the current hop. When encrypting the -// mix header we essentially do the reverse of this operation: we "encrypt" the -// padding, and drop 'hopSize' number of zeroes. As nodes process the mix -// header they add the padding ('hopSize') in order to check the MAC and -// decrypt the next routing information eventually leaving only the original -// "filler" bytes produced by this function at the last hop. Using this -// methodology, the size of the field stays constant at each hop. -func generateHeaderPadding(key string, numHops int, hopSize int, - sharedSecrets []Hash256) []byte { - - filler := make([]byte, (numHops-1)*hopSize) - for i := 1; i < numHops; i++ { - totalFillerSize := ((NumMaxHops - i) + 1) * hopSize - - streamKey := generateKey(key, &sharedSecrets[i-1]) +// 'frameSize*frames' padding of zeroes, concatenate it to the previous filler, +// then decrypt it (XOR) with the secret key of the current hop. When +// encrypting the mix header we essentially do the reverse of this operation: +// we "encrypt" the padding, and drop 'frameSize*frames' number of zeroes. As +// nodes process the mix header they add the padding ('frameSize*frames') in +// order to check the MAC and decrypt the next routing information eventually +// leaving only the original "filler" bytes produced by this function at the +// last hop. Using this methodology, the size of the field stays constant at +// each hop. +func generateHeaderPadding(key string, path *PaymentPath, sharedSecrets []Hash256) []byte { + numHops := path.TrueRouteLength() + + // We have to generate a filler that matches all but the last hop (the + // last hop won't generate an HMAC) + fillerSize := path.TotalPayloadSize() - path[numHops-1].HopPayload.NumBytes() + filler := make([]byte, fillerSize) + + for i := 0; i < numHops-1; i++ { + // Sum up how many frames were used by prior hops. + fillerStart := routingInfoSize + for _, p := range path[:i] { + fillerStart -= p.HopPayload.NumBytes() + } + + // The filler is the part dangling off of the end of the + // routingInfo, so offset it from there, and use the current + // hop's frame count as its size. + fillerEnd := routingInfoSize + path[i].HopPayload.NumBytes() + + streamKey := generateKey(key, &sharedSecrets[i]) streamBytes := generateCipherStream(streamKey, numStreamBytes) - xor(filler, filler, streamBytes[totalFillerSize:totalFillerSize+i*hopSize]) + xor(filler, filler, streamBytes[fillerStart:fillerEnd]) } + return filler } @@ -509,7 +432,13 @@ type ProcessedPacket struct { // // NOTE: This field will only be populated iff the above Action is // MoreHops. - ForwardingInstructions HopData + ForwardingInstructions *HopData + + // Payload is the raw payload as extracted from the packet. If the + // ForwardingInstructions field above is nil, then this is a modern TLV + // payload. As a result, the caller should parse the contents to obtain + // the new set of forwarding instructions. + Payload HopPayload // NextPacket is the onion packet that should be forwarded to the next // hop as denoted by the ForwardingInstructions field. @@ -628,7 +557,7 @@ func (r *Router) ReconstructOnionPacket(onionPkt *OnionPacket, // packet. This function returns the next inner onion packet layer, along with // the hop data extracted from the outer onion packet. func unwrapPacket(onionPkt *OnionPacket, sharedSecret *Hash256, - assocData []byte) (*OnionPacket, *HopData, error) { + assocData []byte) (*OnionPacket, *HopPayload, error) { dhKey := onionPkt.EphemeralKey routeInfo := onionPkt.RoutingInfo @@ -647,10 +576,9 @@ func unwrapPacket(onionPkt *OnionPacket, sharedSecret *Hash256, // layer off the routing info revealing the routing information for the // next hop. streamBytes := generateCipherStream( - generateKey("rho", sharedSecret), - numStreamBytes, + generateKey("rho", sharedSecret), numStreamBytes, ) - zeroBytes := bytes.Repeat([]byte{0}, HopDataSize) + zeroBytes := bytes.Repeat([]byte{0}, MaxPayloadSize) headerWithPadding := append(routeInfo[:], zeroBytes...) var hopInfo [numStreamBytes]byte @@ -662,25 +590,25 @@ func unwrapPacket(onionPkt *OnionPacket, sharedSecret *Hash256, nextDHKey := blindGroupElement(dhKey, blindingFactor[:]) // With the MAC checked, and the payload decrypted, we can now parse - // out the per-hop data so we can derive the specified forwarding + // out the payload so we can derive the specified forwarding // instructions. - var hopData HopData - if err := hopData.Decode(bytes.NewReader(hopInfo[:])); err != nil { + var hopPayload HopPayload + if err := hopPayload.Decode(bytes.NewReader(hopInfo[:])); err != nil { return nil, nil, err } // With the necessary items extracted, we'll copy of the onion packet // for the next node, snipping off our per-hop data. var nextMixHeader [routingInfoSize]byte - copy(nextMixHeader[:], hopInfo[HopDataSize:]) + copy(nextMixHeader[:], hopInfo[hopPayload.NumBytes():]) innerPkt := &OnionPacket{ Version: onionPkt.Version, EphemeralKey: nextDHKey, RoutingInfo: nextMixHeader, - HeaderMAC: hopData.HMAC, + HeaderMAC: hopPayload.HMAC, } - return innerPkt, &hopData, nil + return innerPkt, &hopPayload, nil } // processOnionPacket performs the primary key derivation and handling of onion @@ -697,7 +625,7 @@ func processOnionPacket(onionPkt *OnionPacket, sharedSecret *Hash256, // mix header is the one that we'll want to pass onto the next hop so // they can properly check the HMAC and unwrap a layer for their // handoff hop. - innerPkt, outerHopData, err := unwrapPacket( + innerPkt, outerHopPayload, err := unwrapPacket( onionPkt, sharedSecret, assocData, ) if err != nil { @@ -708,16 +636,22 @@ func processOnionPacket(onionPkt *OnionPacket, sharedSecret *Hash256, // However if the uncovered 'nextMac' is all zeroes, then this // indicates that we're the final hop in the route. var action ProcessCode = MoreHops - if bytes.Compare(zeroHMAC[:], outerHopData.HMAC[:]) == 0 { + if bytes.Compare(zeroHMAC[:], outerHopPayload.HMAC[:]) == 0 { action = ExitNode } + hopData, err := outerHopPayload.HopData() + if err != nil { + return nil, err + } + // Finally, we'll return a fully processed packet with the outer most // hop data (where the primary forwarding instructions lie) and the // inner most onion packet that we unwrapped. return &ProcessedPacket{ Action: action, - ForwardingInstructions: *outerHopData, + ForwardingInstructions: hopData, + Payload: *outerHopPayload, NextPacket: innerPkt, }, nil } diff --git a/sphinx_test.go b/sphinx_test.go index 03e07b3..37c1d50 100644 --- a/sphinx_test.go +++ b/sphinx_test.go @@ -3,7 +3,9 @@ package sphinx import ( "bytes" "encoding/hex" + "encoding/json" "fmt" + "io/ioutil" "reflect" "testing" @@ -108,21 +110,25 @@ func newTestRoute(numHops int) ([]*Router, *PaymentPath, *[]HopData, *OnionPacke ) for i := 0; i < len(nodes); i++ { hopData := HopData{ - Realm: [1]byte{0x00}, ForwardAmount: uint64(i), OutgoingCltv: uint32(i), } - copy(hopData.NextAddress[:], bytes.Repeat([]byte{byte(i)}, 8)) + hopPayload, err := NewHopPayload(&hopData, nil) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("unable to "+ + "create new hop payload: %v", err) + } + route[i] = OnionHop{ - NodePub: *nodes[i].onionKey.PubKey(), - HopData: hopData, + NodePub: *nodes[i].onionKey.PubKey(), + HopPayload: hopPayload, } } // Generate a forwarding message to route to the final node via the - // generated intermdiates nodes above. Destination should be Hash160, + // generated intermediate nodes above. Destination should be Hash160, // adding padding so parsing still works. sessionKey, _ := btcec.PrivKeyFromBytes( btcec.S256(), bytes.Repeat([]byte{'A'}, 32), @@ -135,7 +141,13 @@ func newTestRoute(numHops int) ([]*Router, *PaymentPath, *[]HopData, *OnionPacke var hopsData []HopData for i := 0; i < len(nodes); i++ { - hopsData = append(hopsData, route[i].HopData) + hopData, err := route[i].HopPayload.HopData() + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("unable to "+ + "gen hop data: %v", err) + } + + hopsData = append(hopsData, *hopData) } return nodes, &route, &hopsData, fwdMsg, nil @@ -158,18 +170,22 @@ func TestBolt4Packet(t *testing.T) { } hopData := HopData{ - Realm: [1]byte{0x00}, ForwardAmount: uint64(i), OutgoingCltv: uint32(i), } copy(hopData.NextAddress[:], bytes.Repeat([]byte{byte(i)}, 8)) hopsData = append(hopsData, hopData) + hopPayload, err := NewHopPayload(&hopData, nil) + if err != nil { + t.Fatalf("unable to make hop payload: %v", err) + } + pubKey.Curve = nil route[i] = OnionHop{ - NodePub: *pubKey, - HopData: hopData, + NodePub: *pubKey, + HopPayload: hopPayload, } } @@ -222,7 +238,7 @@ func TestSphinxCorrectness(t *testing.T) { // The hop data for this hop should *exactly* match what was // initially used to construct the packet. expectedHopData := (*hopDatas)[i] - if !reflect.DeepEqual(onionPacket.ForwardingInstructions, expectedHopData) { + if !reflect.DeepEqual(*onionPacket.ForwardingInstructions, expectedHopData) { t.Fatalf("hop data doesn't match: expected %v, got %v", spew.Sdump(expectedHopData), spew.Sdump(onionPacket.ForwardingInstructions)) @@ -502,3 +518,397 @@ func TestSphinxEncodeDecode(t *testing.T) { spew.Sdump(fwdMsg), spew.Sdump(newFwdMsg)) } } + +func newEOBRoute(numHops uint32, + eobMapping map[int]HopPayload) (*OnionPacket, []*Router, error) { + + nodes := make([]*Router, numHops) + + if uint32(len(eobMapping)) != numHops { + return nil, nil, fmt.Errorf("must provide payload " + + "mapping for all hops") + } + + // First, we'll assemble a set of routers that will consume all the + // hops we create in this path. + for i := 0; i < len(nodes); i++ { + privKey, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + return nil, nil, fmt.Errorf("Unable to generate "+ + "random key for sphinx node: %v", err) + } + + nodes[i] = NewRouter( + privKey, &chaincfg.MainNetParams, NewMemoryReplayLog(), + ) + } + + // Next we'll gather all the pubkeys in the path, checking our eob + // mapping to see which hops need an extra payload. + var ( + route PaymentPath + ) + for i := 0; i < len(nodes); i++ { + route[i] = OnionHop{ + NodePub: *nodes[i].onionKey.PubKey(), + HopPayload: eobMapping[i], + } + } + + // Generate a forwarding message to route to the final node via the + // generated intermdiates nodes above. Destination should be Hash160, + // adding padding so parsing still works. + sessionKey, _ := btcec.PrivKeyFromBytes( + btcec.S256(), bytes.Repeat([]byte{'A'}, 32), + ) + fwdMsg, err := NewOnionPacket(&route, sessionKey, nil) + if err != nil { + return nil, nil, fmt.Errorf("unable to create forwarding "+ + "message: %#v", err) + } + + return fwdMsg, nodes, nil +} + +func mustNewHopPayload(hopData *HopData, eob []byte) HopPayload { + payload, err := NewHopPayload(hopData, eob) + if err != nil { + panic(err) + } + + return payload +} + +// TestSphinxHopVariableSizedPayloads tests that we're able to fully decode an +// EOB payload that was targeted at the final hop in a route, and also when +// intermediate nodes have EOB data encoded as well. Additionally, we test that +// we're able to mix the legacy and current format within the same route. +func TestSphinxHopVariableSizedPayloads(t *testing.T) { + t.Parallel() + + var testCases = []struct { + numNodes uint32 + eobMapping map[int]HopPayload + }{ + // A single hop route with a payload going to the last hop in + // the route. The payload is enough to fit into what would be + // the normal frame type, but it's a TLV hop. + { + numNodes: 1, + eobMapping: map[int]HopPayload{ + 0: HopPayload{ + Type: PayloadTLV, + Payload: bytes.Repeat([]byte("a"), HopDataSize-HMACSize), + }, + }, + }, + + // A single hop route where the payload to the final node needs + // to shift more than a single frame. + { + numNodes: 1, + eobMapping: map[int]HopPayload{ + 0: HopPayload{ + Type: PayloadTLV, + Payload: bytes.Repeat([]byte("a"), HopDataSize*3), + }, + }, + }, + + // A two hop route, so one going over 3 nodes, with the sender + // encrypting a payload to the final node. The payload of the + // final node will require more shifts than normal to parse the + // data The first hop is a legacy hop containing the usual + // amount of data. + { + numNodes: 2, + eobMapping: map[int]HopPayload{ + 0: mustNewHopPayload(&HopData{ + Realm: [1]byte{0x00}, + ForwardAmount: 2, + OutgoingCltv: 3, + NextAddress: [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + }, nil), + 1: HopPayload{ + Type: PayloadTLV, + Payload: bytes.Repeat([]byte("a"), HopDataSize*2), + }, + }, + }, + + // A 3 hop route (4 nodes) with all but the middle node + // receiving a TLV payload. Each of the TLV hops will use a + // distinct amount of data in each hop. + { + numNodes: 3, + eobMapping: map[int]HopPayload{ + 0: HopPayload{ + Type: PayloadTLV, + Payload: bytes.Repeat([]byte("a"), 100), + }, + 1: mustNewHopPayload(&HopData{ + Realm: [1]byte{0x00}, + ForwardAmount: 22, + OutgoingCltv: 9, + NextAddress: [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + }, nil), + 2: HopPayload{ + Type: PayloadTLV, + Payload: bytes.Repeat([]byte("a"), 256), + }, + }, + }, + + // A 3 hop route (4 nodes), each hop is a TLV hop and will use + // a distinct amount of data for each of their hops. + { + numNodes: 3, + eobMapping: map[int]HopPayload{ + 0: HopPayload{ + Type: PayloadTLV, + Payload: bytes.Repeat([]byte("a"), 200), + }, + 1: HopPayload{ + Type: PayloadTLV, + Payload: bytes.Repeat([]byte("a"), 256), + }, + 2: HopPayload{ + Type: PayloadTLV, + Payload: bytes.Repeat([]byte("a"), 150), + }, + }, + }, + } + + for testCaseNum, testCase := range testCases { + nextPkt, routers, err := newEOBRoute( + testCase.numNodes, testCase.eobMapping, + ) + if err != nil { + t.Fatalf("#%v: unable to create eob "+ + "route: %v", testCase, err) + } + + // We'll now walk thru manually each actual hop within the + // route. We use the size of the routers rather than the number + // of hops here as virtual EOB hops may have been inserted into + // the route. + for i := 0; i < len(routers); i++ { + // Start each node's ReplayLog and defer shutdown + routers[i].log.Start() + defer routers[i].log.Stop() + + currentHop := routers[i] + + // Ensure that this hop is able to properly process + // this onion packet. If additional EOB hops were + // added, then it should be able to properly decrypt + // all the layers and pass them on to the next node + // properly. + processedPacket, err := currentHop.ProcessOnionPacket( + nextPkt, nil, uint32(i), + ) + if err != nil { + t.Fatalf("#%v: unable to process packet at "+ + "hop #%v: %v", testCaseNum, i, err) + } + + // If this hop is expected to have EOB data, then we'll + // check now to ensure the bytes were properly + // recovered on the other end. + eobData := testCase.eobMapping[i] + if !reflect.DeepEqual(eobData.Payload, + processedPacket.Payload.Payload) { + t.Fatalf("#%v (hop %v): eob mismatch: expected "+ + "%v, got %v", testCaseNum, i, + spew.Sdump(eobData.Payload), + spew.Sdump(processedPacket.Payload.Payload)) + } + + if eobData.Type != processedPacket.Payload.Type { + t.Fatalf("mismatched types: expected %v "+ + "got %v", eobData.Type, + processedPacket.Payload.Type) + } + + // If this is the last node (but not necessarily hop + // due to EOB expansion), then it should recognize that + // it's the exit node. + if i == len(routers)-1 { + if processedPacket.Action != ExitNode { + t.Fatalf("#%v: Processing error, "+ + "node %v is the last hop in "+ + "the path, yet it doesn't "+ + "recognize so", testCaseNum, i) + } + continue + } + + // If this isn't the last node in the path, then the + // returned action should indicate that there are more + // hops to go. + if processedPacket.Action != MoreHops { + t.Fatalf("#%v: Processing error, node %v is "+ + "not the final hop, yet thinks it is.", + testCaseNum, i) + } + + // The next hop should have been parsed as node[i+1], + // but only if this was a legacy hop. + if processedPacket.ForwardingInstructions != nil { + parsedNextHop := processedPacket.ForwardingInstructions.NextAddress[:] + + expected := bytes.Repeat([]byte{byte(1)}, AddressSize) + if !bytes.Equal(parsedNextHop, expected) { + t.Fatalf("#%v: Processing error, next hop parsed "+ + "incorrectly. next hop should be %v, "+ + "was instead parsed as %v", testCaseNum, + hex.EncodeToString(expected), + hex.EncodeToString(parsedNextHop)) + } + } + + nextPkt = processedPacket.NextPacket + } + } +} + +// testFileName is the name of the multi-frame onion test file. +const testFileName = "testdata/onion-test-multi-frame.json" + +type jsonHop struct { + Type string `json:"type"` + + Pubkey string `json:"pubkey"` + + Payload string `json:"payload"` +} + +type payloadTestCase struct { + SessionKey string `json:"session_key"` + + AssociatedData string `json:"associated_data"` + + Hops []jsonHop `json:"hops"` +} + +type jsonTestCase struct { + Comment string `json:"comment"` + + Generate payloadTestCase `json:"generate"` + + Onion string `json:"onion"` + + Decode []string `json:"decode"` +} + +// jsonTypeToPayloadType maps the JSON payload type to our concrete PayloadType +// type. +func jsonTypeToPayloadType(jsonType string) PayloadType { + switch jsonType { + case "raw": + fallthrough + case "tlv": + return PayloadTLV + + case "legacy": + return PayloadLegacy + + default: + panic(fmt.Sprintf("unknown payload type: %v", jsonType)) + } +} + +// TestVariablePayloadOnion tests that if we construct a packet that contains a +// mix of the old and new payload format, that we match the version that's +// included in the spec. +func TestVariablePayloadOnion(t *testing.T) { + t.Parallel() + + // First, we'll read out the raw JSOn file at the target location. + jsonBytes, err := ioutil.ReadFile(testFileName) + if err != nil { + t.Fatalf("unable to read json file: %v", err) + } + + // Once we have the raw file, we'll unpack it into our jsonTestCase + // struct defined above. + testCase := &jsonTestCase{} + if err := json.Unmarshal(jsonBytes, testCase); err != nil { + t.Fatalf("unable to parse spec json file: %v", err) + } + + // Next, we'll populate a new OnionHop using the information included + // in this test case. + var route PaymentPath + for i, hop := range testCase.Generate.Hops { + pubKeyBytes, err := hex.DecodeString(hop.Pubkey) + if err != nil { + t.Fatalf("unable to decode pubkey: %v", err) + } + pubKey, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256()) + if err != nil { + t.Fatalf("unable to parse BOLT 4 pubkey #%d: %v", i, err) + } + + payload, err := hex.DecodeString(hop.Payload) + if err != nil { + t.Fatalf("unable to decode payload: %v", err) + } + + payloadType := jsonTypeToPayloadType(hop.Type) + route[i] = OnionHop{ + NodePub: *pubKey, + HopPayload: HopPayload{ + Type: payloadType, + Payload: payload, + }, + } + + if payloadType == PayloadLegacy { + route[i].HopPayload.Payload = append( + []byte{0x00}, route[i].HopPayload.Payload..., + ) + + route[i].HopPayload.Payload = append( + route[i].HopPayload.Payload, + bytes.Repeat([]byte{0x00}, NumPaddingBytes)..., + ) + } + } + + finalPacket, err := hex.DecodeString(testCase.Onion) + if err != nil { + t.Fatalf("unable to decode packet: %v", err) + } + + sessionKeyBytes, err := hex.DecodeString(testCase.Generate.SessionKey) + if err != nil { + t.Fatalf("unable to generate session key: %v", err) + } + + associatedData, err := hex.DecodeString(testCase.Generate.AssociatedData) + if err != nil { + t.Fatalf("unable to decode AD: %v", err) + } + + // With all the required data assembled, we'll craft a new packet. + sessionKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), sessionKeyBytes) + pkt, err := NewOnionPacket(&route, sessionKey, associatedData) + if err != nil { + t.Fatalf("unable to construct onion packet: %v", err) + } + + var b bytes.Buffer + if err := pkt.Encode(&b); err != nil { + t.Fatalf("unable to decode onion packet: %v", err) + } + + // Finally, we expect that our packet matches the packet included in + // the spec's test vectors. + if bytes.Compare(b.Bytes(), finalPacket) != 0 { + t.Fatalf("final packet does not match expected BOLT 4 packet, "+ + "want: %s, got %s", hex.EncodeToString(finalPacket), + hex.EncodeToString(b.Bytes())) + } +} diff --git a/testdata/onion-test-multi-frame.json b/testdata/onion-test-multi-frame.json new file mode 100644 index 0000000..495de9e --- /dev/null +++ b/testdata/onion-test-multi-frame.json @@ -0,0 +1,42 @@ +{ + "comment": "A testcase for a variable length hop_payload. The third payload is 256 bytes long.", + "generate": { + "session_key": "4141414141414141414141414141414141414141414141414141414141414141", + "associated_data": "4242424242424242424242424242424242424242424242424242424242424242", + "hops": [ + { + "type": "legacy", + "pubkey": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619", + "payload": "0000000000000000000000000000000000000000" + }, + { + "type": "tlv", + "pubkey": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c", + "payload": "0101010101010101000000000000000100000001" + }, + { + "type": "raw", + "pubkey": "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007", + "payload": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" + }, + { + "type": "tlv", + "pubkey": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", + "payload": "0303030303030303000000000000000300000003" + }, + { + "type": "legacy", + "pubkey": "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145", + "payload": "0404040404040404000000000000000400000004" + } + ] + }, + "onion": "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71a060daf367132b378b3a3883c0e2c0e026b8900b2b5cdbc784e1a3bb913f88a9c50f7d61ab590531cf08000178a333a347f8b4072ed056f820f77774345e183a342ec4729f3d84accf515e88adddb85ecc08daba68404bae9a8e8d7178977d7094a1ae549f89338c0777551f874159eb42d3a59fb9285ad4e24883f27de23942ec966611e99bee1cee503455be9e8e642cef6cef7b9864130f692283f8a973d47a8f1c1726b6e59969385975c766e35737c8d76388b64f748ee7943ffb0e2ee45c57a1abc40762ae598723d21bd184e2b338f68ebff47219357bd19cd7e01e2337b806ef4d717888e129e59cd3dc31e6201ccb2fd6d7499836f37a993262468bcb3a4dcd03a22818aca49c6b7b9b8e9e870045631d8e039b066ff86e0d1b7291f71cefa7264c70404a8e538b566c17ccc5feab231401e6c08a01bd5edfc1aa8e3e533b96e82d1f91118d508924b923531929aea889fcdf050597c681185f336b1da63b0939aa2b7c50b21b5eb7b6ad66c81fab98a3cdf73f658149e7e9ced4edde5d38c9b8f92e16f6b4ab13d7fca6a0e4ecc9f9de611a90da6e99c39551094c56e3196f282c5dffd9fc4b2fc12f3bca8e6fe47eb45fbdd3be21a8a8d200797eae3c9a0497132f92410d804977408494dff49dd3d8bce248e0b74fd9e6f0f7102c25ddfa02bd9ad9f746abbfa337ef811d5345a9e16b60de1767b209645ba40bd1f9a5f75bc04feca9b27c5554be4fe83fac2cb83aa447a817bb85ae966c68b420063833fada375e2f515965e687a45699632902672c654d1d18d7bcbf55e8fa57f63f2da449f8e1e606e8722df081e5f193fc4179feb99ad22819afdeef211f7c54afdba92aeef0c00b7bc2b65a4813c01f907a8377585708f2d4c940a25328e585714c8ded0a9a4d7a6de1027c1cb7a0198cd3db68b58c0704dfd0cfbe624e9cd18cc0ae5d96697bb476708b9ee0403d211e64e0d5a7683a7a9a140c02f0ff1c6e67a302941b4052bdea8a63e70a3ad62c5b89c698f1fd3c7685cb49705096cad702d02d93bcb1c27a409f4c9bddec001205ca4a2740f19b50900be81c7e847f1a863deea8d35701f1355cad8db57b1d4eb2ab4e29587734785abfb46ddede71928213d7d089dfdeda052827f459f1688cc0935bd47e7bcec27427c8376dcce7e22699567c0d145f8a7db33f6758815f1f15f9f7a9760dec4f34ae095edda4c64e9735bdd029c4e32c2ee31ba47ec5e6bdb97813d52dbd15b4e0b7a2c7f790ae64104d99f38c127f0a093288fa34144adb16b8968d4fa7656fcec99de8503dd46d3b03620a71c7cd085364abd30dccf7fbda25a1cdc102600149c9af1c97aa0372cd2e1909f28ac5c686f432b310e79528c9b8b9e8f314c1e74621ce6308ad2278b81d460892e0d9dd38b7c76d58be6dfd10ae7583ee1e7ef5b3f6f78dc60af0950df1b00cc55b6d178ba2e476bea0eaeef49323b83f05804159e7aef4eed4cc60dd07be76f067dfd0bcfb0b806b69ba921336a20c43c832d0cab8fa3ddeb29e3bf07b0d98a112eb07802756235a49d44a8b82a950d84e95e01971f0e106ccb337f07384e21620e0ad39e16ed9edca123226cf55ac44f449eeb53e38a7f27d101806e4823e4efcc887414240ee6826c4a5cb1c6443ad36ebf905a435c1d9054e54173911b17b5b40f60b3d9fd5f12eac54ca1e20191f5f18544d5fd3d665e9bcef96fb44b76110aa64d9db4c86c9513cbdad546538e8aec521fbe83ceac5e74a15629f1ed0b870a1d0d1e5680b6d6100d1bd3f3b9043bd35b8919c4088f1949b8be89e4701eb870f8ed64fafa446c78df3ea", + "decode": [ + "4141414141414141414141414141414141414141414141414141414141414141", + "4242424242424242424242424242424242424242424242424242424242424242", + "4343434343434343434343434343434343434343434343434343434343434343", + "4444444444444444444444444444444444444444444444444444444444444444", + "4545454545454545454545454545454545454545454545454545454545454545" + ] +}