From e1eabc317c5ec52b06b13c660ed035a4fdaa96a7 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 23 Jul 2019 17:44:24 -0700 Subject: [PATCH 01/18] build: point to latest lnd w/ TLV package --- go.mod | 10 +-- go.sum | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 202 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 67d14e7..a6796d2 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 // indirect + golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 ) diff --git a/go.sum b/go.sum index 256e849..a596b77 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,217 @@ +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/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/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/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/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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +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/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/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/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/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/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/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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From d01f01709f6a9ebcf9dfb1042dda95f146faf54f Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Mon, 4 Feb 2019 19:37:07 +0100 Subject: [PATCH 02/18] sphinx: Introduce HopPayload as opaque, variable length, byte-slice We'll be encoding to and decoding from a variable length byte-slice once we complete the multi-hop proposal. Signed-off-by: Christian Decker --- path.go | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++- path_test.go | 39 ++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 path_test.go diff --git a/path.go b/path.go index 3d41886..a3ee8b2 100644 --- a/path.go +++ b/path.go @@ -1,6 +1,10 @@ package sphinx -import "github.com/btcsuite/btcd/btcec" +import ( + "math" + + "github.com/btcsuite/btcd/btcec" +) // NumMaxHops is the maximum path length. This should be set to an estimate of // the upper limit of the diameter of the node graph. @@ -16,6 +20,37 @@ const NumMaxHops = 20 // information. type PaymentPath [NumMaxHops]OnionHop +// 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 datafield by the onion router, while the Realm is modified to +// indicate how many hops are to be read by the processing node. The 4 MSB in +// the realm indicate how many additional hops are to be processed to collect +// the entire payload. +type HopPayload struct { + Realm [1]byte + + Payload []byte + + HMAC [HMACSize]byte +} + +// NumFrames returns the total number of frames it'll take to pack the target +// HopPayload into a Sphinx packet. +func (hp *HopPayload) NumFrames() int { + // If it all fits in the legacy payload size, don't use any additional + // frames. + if len(hp.Payload) <= 32 { + return 1 + } + + // Otherwise we'll need at least one additional frame: subtract the 64 + // bytes we can stuff into payload and hmac of the first, and the 33 + // bytes we can pack into the payload of the second, then divide the + // remainder by 65. + remainder := len(hp.Payload) - 64 - 33 + return 2 + int(math.Ceil(float64(remainder)/65)) +} + // OnionHop represents an abstract hop (a link between two nodes) within the // Lightning Network. A hop is composed of the incoming node (able to decrypt // the encrypted routing information), and the routing information itself. @@ -31,6 +66,10 @@ type OnionHop struct { // 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 +109,39 @@ func (p *PaymentPath) TrueRouteLength() int { return routeLength } + +// TotalFrames returns the total numebr of frames that it'll take to create a +// Sphinx packet from the target PaymentPath. +func (p *PaymentPath) TotalFrames() int { + var frameCount int + for _, hop := range p { + if hop.IsEmpty() { + break + } + + frameCount += hop.HopPayload.NumFrames() + } + + return frameCount +} + +const ( + // RealmMaskBytes is the mask to apply the realm in order to pack or + // decode the 4 LSB of the realm field. + RealmMaskBytes = 0x0f + + // NumFramesShift is the number of bytes to shift the encoding of the + // number of frames by in order to pack/unpack them into the 4 MSB bits + // of the realm field. + NumFramesShift = 4 +) + +// CalculateRealm computes the proper realm encoding in place. The final +// encoding uses the first 4 bits of the realm to encode the number of frames +// used, and the latter 4 bits to encode the real realm type. +func (hp *HopPayload) CalculateRealm() { + maskedRealm := hp.Realm[0] & 0x0F + numFrames := hp.NumFrames() + + hp.Realm[0] = maskedRealm | (byte(numFrames-1) << NumFramesShift) +} diff --git a/path_test.go b/path_test.go new file mode 100644 index 0000000..fc9ca22 --- /dev/null +++ b/path_test.go @@ -0,0 +1,39 @@ +package sphinx + +import ( + "bytes" + "testing" +) + +func TestHopPayloadSizes(t *testing.T) { + var tests = []struct { + size int + expected int + realm byte + }{ + {30, 1, 0x01}, + {32, 1, 0x01}, + {33, 2, 0x11}, + {97, 2, 0x11}, // The largest possible 2-hop payload + {98, 3, 0x21}, + {162, 3, 0x21}, + {163, 4, 0x31}, + } + + for _, tt := range tests { + hp := HopPayload{ + Realm: [1]byte{1}, + Payload: bytes.Repeat([]byte{0x00}, tt.size), + } + + actual := hp.NumFrames() + if actual != tt.expected { + t.Errorf("Wrong number of hops returned: expected %d, actual %d", tt.expected, actual) + } + + hp.CalculateRealm() + if hp.Realm[0] != tt.realm { + t.Errorf("Updated realm did not match our expectation: expected %q, actual %q", tt.realm, hp.Realm) + } + } +} From 090594888a17b389b09449fba1b6440d69c918e3 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Tue, 5 Feb 2019 17:16:16 +0100 Subject: [PATCH 03/18] multi-frame: Introduce frameSize constant to differentiate hops from frames Since we want to use multiple former per-hop-payloads as a single payload, we want to differentiate the notion of a frame from a payload, since the latter can be now be made up of multiple frames. Signed-off-by: Christian Decker <@cdecker> --- sphinx.go | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/sphinx.go b/sphinx.go index b10100a..f86537e 100644 --- a/sphinx.go +++ b/sphinx.go @@ -44,6 +44,25 @@ const ( HopDataSize = (RealmByteSize + AddressSize + AmtForwardSize + OutgoingCLTVSize + NumPaddingBytes + HMACSize) + // FramSize is the size of a frame in the packet. A frame is a region + // of contiguous memory in the onion that can be used to store a + // payload in. Each hop may use a discrete number of frames for its + // payload. There are two parts of the payload that are fixed: a) the + // first byte in the first frame is the realm-byte, which tells us how + // many frames the current payload uses and how the payload should be + // parsed, and b) the last 32 bytes of the last frame are the HMAC that + // should be passed to the next hop, or 0 in case of the last hop. + // Bytes between these two points can be freely used to store the + // actual payload. + FrameSize = 65 + + // 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 * FrameSize + // sharedSecretSize is the size in bytes of the shared secrets. sharedSecretSize = 32 @@ -52,13 +71,14 @@ const ( // each hop of the route, the first pair in cleartext and the following // pairs increasingly obfuscated. In case fewer than numMaxHops are // used, then the remainder is padded with null-bytes, also obfuscated. - routingInfoSize = NumMaxHops * HopDataSize + routingInfoSize = NumMaxHops * FrameSize // 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 + MaxPayloadSize // keyLen is the length of the keys used to generate cipher streams and // encrypt payloads. Since we use SHA256 to generate the keys, the @@ -288,7 +308,7 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, // Generate the padding, called "filler strings" in the paper. filler := generateHeaderPadding( - "rho", numHops, HopDataSize, hopSharedSecrets, + "rho", numHops, FrameSize, hopSharedSecrets, ) // Allocate zero'd out byte slices to store the final mix header packet @@ -321,7 +341,7 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, // Before we assemble the packet, we'll shift the current // mix-header to the write in order to make room for this next // per-hop data. - rightShift(mixHeader[:], HopDataSize) + rightShift(mixHeader[:], FrameSize) // With the mix header right-shifted, we'll encode the current // hop data into a buffer we'll re-use during the packet @@ -650,7 +670,7 @@ func unwrapPacket(onionPkt *OnionPacket, sharedSecret *Hash256, generateKey("rho", sharedSecret), numStreamBytes, ) - zeroBytes := bytes.Repeat([]byte{0}, HopDataSize) + zeroBytes := bytes.Repeat([]byte{0}, FrameSize) headerWithPadding := append(routeInfo[:], zeroBytes...) var hopInfo [numStreamBytes]byte @@ -672,7 +692,7 @@ func unwrapPacket(onionPkt *OnionPacket, sharedSecret *Hash256, // 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[FrameSize:]) innerPkt := &OnionPacket{ Version: onionPkt.Version, EphemeralKey: nextDHKey, From 5bd403516413954c42fcf2e159a5d22bb2418b3d Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Mon, 4 Feb 2019 23:03:15 +0100 Subject: [PATCH 04/18] multi-frame: Use HopPayload instead of HopData HopPayload represents the raw, unparsed payload, from which the HopData can be parsed. The sphinx code internally only handles HopPayload, and the `HopData()` method can be used to parse and return the HopData. This completes the transition to HopPayload. Signed-off-by: Christian Decker --- path.go | 167 +++++++++++++++++++++++++++++++++++++++---------- sphinx.go | 88 +++++++++++--------------- sphinx_test.go | 2 +- 3 files changed, 171 insertions(+), 86 deletions(-) diff --git a/path.go b/path.go index a3ee8b2..5ca37aa 100644 --- a/path.go +++ b/path.go @@ -1,24 +1,25 @@ package sphinx import ( + "bytes" + "encoding/binary" + "fmt" + "io" "math" "github.com/btcsuite/btcd/btcec" ) -// NumMaxHops is the maximum path length. This should be set to an estimate of -// the upper limit of the diameter of the node graph. -const NumMaxHops = 20 +const ( + // RealmMaskBytes is the mask to apply the realm in order to pack or + // decode the 4 LSB of the realm field. + RealmMaskBytes = 0x0f -// PaymentPath represents a series of hops within the Lightning Network -// starting at a sender and terminating at a receiver. Each hop contains a set -// of mandatory data which contains forwarding instructions for that hop. -// Additionally, we can also transmit additional data to each hop by utilizing -// the un-used hops (see TrueRouteLength()) to pack in additional data. In -// order to do this, we encrypt the several hops with the same node public key, -// and unroll the extra data into the space used for route forwarding -// information. -type PaymentPath [NumMaxHops]OnionHop + // NumFramesShift is the number of bytes to shift the encoding of the + // number of frames by in order to pack/unpack them into the 4 MSB bits + // of the realm field. + NumFramesShift = 4 +) // 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 @@ -51,6 +52,127 @@ func (hp *HopPayload) NumFrames() int { return 2 + int(math.Ceil(float64(remainder)/65)) } +// CalculateRealm computes the proper realm encoding in place. The final +// encoding uses the first 4 bits of the realm to encode the number of frames +// used, and the latter 4 bits to encode the real realm type. +func (hp *HopPayload) CalculateRealm() { + maskedRealm := hp.Realm[0] & 0x0F + numFrames := hp.NumFrames() + + hp.Realm[0] = maskedRealm | (byte(numFrames-1) << NumFramesShift) +} + +// Encode encodes the hop payload into the passed writer. +func (hp *HopPayload) Encode(w io.Writer) error { + // We'll need to add enough padding bytes to position the HMAC at the + // end of the payload + padding := hp.NumFrames()*65 - len(hp.Payload) - 1 - 32 + if padding < 0 { + return fmt.Errorf("cannot have negative padding: %v", padding) + } + + // Before we write the realm out, we need to calculate the current + // realm based on the "true" realm as well as the number of frames it + // takes to compute the hop payload. + hp.CalculateRealm() + + if _, err := w.Write(hp.Realm[:]); err != nil { + return err + } + + if _, err := w.Write(hp.Payload); err != nil { + return err + } + + // If we need to pad out the frame at all, then we'll do so now before + // we write out the HMAC. + if padding > 0 { + _, err := w.Write(bytes.Repeat([]byte{0x00}, padding)) + if 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 { + if _, err := io.ReadFull(r, hp.Realm[:]); err != nil { + return err + } + + numFrames := int(hp.Realm[0]>>NumFramesShift) + 1 + numBytes := (numFrames * FrameSize) - 32 - 1 + + hp.Payload = make([]byte, numBytes) + if _, err := io.ReadFull(r, hp.Payload[:]); err != nil { + return err + } + + if _, err := io.ReadFull(r, 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. +func (hp *HopPayload) HopData() (*HopData, error) { + // If this isn't the "base" realm, then we can't extract the expected + // hop payload structure from the payload. + if hp.Realm[0]&RealmMaskBytes != 0x00 { + return nil, fmt.Errorf("payload is not a HopData payload, "+ + "realm=%d", hp.Realm[0]) + } + + // Now that we know the payload has the structure we expect, we'll + // decode the payload into the HopData. + hd := HopData{ + Realm: [1]byte{hp.Realm[0] & RealmMaskBytes}, + HMAC: hp.HMAC, + } + + r := bytes.NewBuffer(hp.Payload) + if _, err := io.ReadFull(r, hd.NextAddress[:]); err != nil { + return nil, err + } + + if err := binary.Read(r, binary.BigEndian, &hd.ForwardAmount); err != nil { + return nil, err + } + + if err := binary.Read(r, binary.BigEndian, &hd.OutgoingCltv); err != nil { + return nil, err + } + + if _, err := io.ReadFull(r, hd.ExtraBytes[:]); 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. +const NumMaxHops = 20 + +// PaymentPath represents a series of hops within the Lightning Network +// starting at a sender and terminating at a receiver. Each hop contains a set +// of mandatory data which contains forwarding instructions for that hop. +// Additionally, we can also transmit additional data to each hop by utilizing +// the un-used hops (see TrueRouteLength()) to pack in additional data. In +// order to do this, we encrypt the several hops with the same node public key, +// and unroll the extra data into the space used for route forwarding +// information. +type PaymentPath [NumMaxHops]OnionHop + // OnionHop represents an abstract hop (a link between two nodes) within the // Lightning Network. A hop is composed of the incoming node (able to decrypt // the encrypted routing information), and the routing information itself. @@ -124,24 +246,3 @@ func (p *PaymentPath) TotalFrames() int { return frameCount } - -const ( - // RealmMaskBytes is the mask to apply the realm in order to pack or - // decode the 4 LSB of the realm field. - RealmMaskBytes = 0x0f - - // NumFramesShift is the number of bytes to shift the encoding of the - // number of frames by in order to pack/unpack them into the 4 MSB bits - // of the realm field. - NumFramesShift = 4 -) - -// CalculateRealm computes the proper realm encoding in place. The final -// encoding uses the first 4 bits of the realm to encode the number of frames -// used, and the latter 4 bits to encode the real realm type. -func (hp *HopPayload) CalculateRealm() { - maskedRealm := hp.Realm[0] & 0x0F - numFrames := hp.NumFrames() - - hp.Realm[0] = maskedRealm | (byte(numFrames-1) << NumFramesShift) -} diff --git a/sphinx.go b/sphinx.go index f86537e..5324d94 100644 --- a/sphinx.go +++ b/sphinx.go @@ -165,10 +165,6 @@ type HopData struct { // 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 } @@ -185,40 +181,6 @@ func (hd *HopData) Encode(w io.Writer) error { 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 } @@ -314,9 +276,10 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, // 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 + hopDataBuf bytes.Buffer + hopPayloadBuf bytes.Buffer ) // Now we compute the routing information for each hop, along with a @@ -331,6 +294,7 @@ 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].HopPayload.HMAC = nextHmac paymentPath[i].HopData.HMAC = nextHmac // Next, using the key dedicated for our stream cipher, we'll @@ -350,7 +314,15 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, if err != nil { return nil, err } - copy(mixHeader[:], hopDataBuf.Bytes()) + + paymentPath[i].HopPayload.Payload = hopDataBuf.Bytes() + + err = paymentPath[i].HopPayload.Encode(&hopPayloadBuf) + if err != nil { + return nil, err + } + + 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 @@ -371,6 +343,7 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, nextHmac = calcMac(muKey, packet) hopDataBuf.Reset() + hopPayloadBuf.Reset() } return &OnionPacket{ @@ -529,7 +502,12 @@ type ProcessedPacket struct { // // NOTE: This field will only be populated iff the above Action is // MoreHops. - ForwardingInstructions HopData + ForwardingInstructions *HopData + + // RawPayload is the raw (plaintext) payload that was passed to the + // processing node in the onion packet. It provides accessors to get + // the parsed and interpreted data. + RawPayload HopPayload // NextPacket is the onion packet that should be forwarded to the next // hop as denoted by the ForwardingInstructions field. @@ -648,7 +626,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 @@ -682,10 +660,10 @@ 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 } @@ -697,10 +675,10 @@ func unwrapPacket(onionPkt *OnionPacket, sharedSecret *Hash256, 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 @@ -717,7 +695,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 { @@ -728,16 +706,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, + RawPayload: *outerHopPayload, NextPacket: innerPkt, }, nil } diff --git a/sphinx_test.go b/sphinx_test.go index 03e07b3..1d9fe3e 100644 --- a/sphinx_test.go +++ b/sphinx_test.go @@ -222,7 +222,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)) From dfdb1896053280329477c03ff03f28eac19b81bd Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Tue, 5 Feb 2019 17:33:25 +0100 Subject: [PATCH 05/18] multi-frame: Actually use multiple frames for a single payload This is the central commit in this whole series, it modifies the right- and left- shifts to account for multi-frame payloads and it modifies the filler generation code to generate multi-frame fillers if the payload requires it. Signed-off-by: Christian Decker --- sphinx.go | 65 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/sphinx.go b/sphinx.go index 5324d94..40baf4b 100644 --- a/sphinx.go +++ b/sphinx.go @@ -269,9 +269,7 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, ) // Generate the padding, called "filler strings" in the paper. - filler := generateHeaderPadding( - "rho", numHops, FrameSize, 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. @@ -300,12 +298,13 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, // 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) // 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[:], FrameSize) + numFrames := paymentPath[i].HopPayload.NumFrames() + rightShift(mixHeader[:], numFrames*FrameSize) // With the mix header right-shifted, we'll encode the current // hop data into a buffer we'll re-use during the packet @@ -327,7 +326,7 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, // 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. @@ -368,25 +367,39 @@ 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) + fillerFrames := path.TotalFrames() - path[numHops-1].HopPayload.NumFrames() + filler := make([]byte, fillerFrames*FrameSize) + + 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 = fillerStart - (p.HopPayload.NumFrames() * FrameSize) + } + + // 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.NumFrames() * FrameSize) + + 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 } @@ -648,7 +661,7 @@ func unwrapPacket(onionPkt *OnionPacket, sharedSecret *Hash256, generateKey("rho", sharedSecret), numStreamBytes, ) - zeroBytes := bytes.Repeat([]byte{0}, FrameSize) + zeroBytes := bytes.Repeat([]byte{0}, MaxPayloadSize) headerWithPadding := append(routeInfo[:], zeroBytes...) var hopInfo [numStreamBytes]byte @@ -670,7 +683,7 @@ func unwrapPacket(onionPkt *OnionPacket, sharedSecret *Hash256, // 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[FrameSize:]) + copy(nextMixHeader[:], hopInfo[hopPayload.NumFrames()*FrameSize:]) innerPkt := &OnionPacket{ Version: onionPkt.Version, EphemeralKey: nextDHKey, From 51659d96a700d9924984e43cf01eaa001b32d376 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Tue, 5 Feb 2019 20:07:13 +0100 Subject: [PATCH 06/18] multi-frame: Add a test for multi-frame onions This is a simple test that create a 5 hop onion, and the third hop gets a payload that spans 2 frames. Signed-off-by: Christian Decker --- sphinx.go | 4 ++- sphinx_test.go | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/sphinx.go b/sphinx.go index 40baf4b..12a082f 100644 --- a/sphinx.go +++ b/sphinx.go @@ -314,7 +314,9 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, return nil, err } - paymentPath[i].HopPayload.Payload = hopDataBuf.Bytes() + paymentPath[i].HopPayload.Payload = append( + hopDataBuf.Bytes(), paymentPath[i].HopPayload.Payload..., + ) err = paymentPath[i].HopPayload.Encode(&hopPayloadBuf) if err != nil { diff --git a/sphinx_test.go b/sphinx_test.go index 1d9fe3e..761d820 100644 --- a/sphinx_test.go +++ b/sphinx_test.go @@ -87,6 +87,11 @@ var ( ) func newTestRoute(numHops int) ([]*Router, *PaymentPath, *[]HopData, *OnionPacket, error) { + extraPayloadSize := make([]int, numHops) + return newTestVarSizeRoute(numHops, extraPayloadSize) +} + +func newTestVarSizeRoute(numHops int, extraPayloadSize []int) ([]*Router, *PaymentPath, *[]HopData, *OnionPacket, error) { nodes := make([]*Router, numHops) // Create numHops random sphinx nodes. @@ -121,6 +126,15 @@ func newTestRoute(numHops int) ([]*Router, *PaymentPath, *[]HopData, *OnionPacke } } + // If we were told to increase the payload with some extra data do it + // now: + for i := 0; i < route.TrueRouteLength(); i++ { + if extraPayloadSize[i] > 0 { + extra := bytes.Repeat([]byte{'A'}, extraPayloadSize[i]) + route[i].HopPayload.Payload = append(route[i].HopPayload.Payload, extra...) + } + } + // 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. @@ -502,3 +516,62 @@ func TestSphinxEncodeDecode(t *testing.T) { spew.Sdump(fwdMsg), spew.Sdump(newFwdMsg)) } } + +// Create an onion with 5 hops, and the 3rd hop +func TestMultiFrameEncodeDecode(t *testing.T) { + + var payloadtests = []struct { + extraPayload []int + expectedFrames []int + }{ + {[]int{0, 0, 0, 0, 0}, []int{1, 1, 1, 1, 1}}, + {[]int{60, 0, 0, 0, 0}, []int{2, 1, 1, 1, 1}}, + {[]int{0, 0, 0, 0, 60}, []int{1, 1, 1, 1, 2}}, + {[]int{0, 0, 0, 0, 130}, []int{1, 1, 1, 1, 3}}, + {[]int{0, 0, 60, 0, 0}, []int{1, 1, 2, 1, 1}}, + } + + for _, tt := range payloadtests { + t.Run(fmt.Sprintf("%v", tt), func(t *testing.T) { + nodes, path, _, fwdMsg, err := newTestVarSizeRoute(5, tt.extraPayload) + if err != nil { + t.Fatalf("unable to create random onion packet: %v", err) + } + + for i := 0; i < len(nodes); i++ { + // Start each node's ReplayLog and defer shutdown + nodes[i].log.Start() + defer nodes[i].log.Stop() + + hop := nodes[i] + + t.Logf("Processing at hop: %v \n", i) + onionPacket, err := hop.ProcessOnionPacket(fwdMsg, nil, uint32(i)+1) + if err != nil { + t.Fatalf("Node %v was unable to process the "+ + "forwarding message: %v", i, err) + } + + // Check that the framecount matches what we expect + frameCount := onionPacket.RawPayload.NumFrames() + if tt.expectedFrames[i] != frameCount { + t.Fatalf("Incorrect number of payload frames: expected %d, got %d", + tt.expectedFrames[i], + frameCount, + ) + } + + // Check that the payload contents are identical + expected := path[i].HopPayload + if !bytes.Equal(path[i].HopPayload.Payload, expected.Payload) { + t.Fatalf("Processing error, hop-payload parsed incorrectly."+ + " expected %x, got %x", + expected.Payload, + onionPacket.RawPayload.Payload) + } + + fwdMsg = onionPacket.NextPacket + } + }) + } +} From f4e4208a513eb46f9c1fb1a9c504d8fc16b8890b Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Tue, 12 Feb 2019 12:14:37 +0100 Subject: [PATCH 07/18] tool: Add an onion spec in JSON that can be passed in This should make testing a lot easier, since we can just have a bunch of these sitting in the repo and test them. Signed-off-by: Christian Decker --- cmd/main.go | 108 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 30 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 8d1b413..fca8464 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,61 @@ 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 + + path[i].HopPayload.Realm[0] = byte(hop.Realm) + path[i].HopPayload.Payload, err = hex.DecodeString(hop.Payload) + if err != nil { + log.Fatalf("%s is not a valid hex payload %s", hop.Payload, err) + } + + 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 +78,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()) + 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) } - sessionKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), bytes.Repeat([]byte{'A'}, 32)) + path, sessionKey, err := parseOnionSpec(spec) + if err != nil { + log.Fatalf("could not parse onion spec: %v", err) + } - msg, err := sphinx.NewOnionPacket(&path, sessionKey, assocData) + 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 +123,11 @@ func main() { } privkey, _ := btcec.PrivKeyFromBytes(btcec.S256(), binKey) - s := sphinx.NewRouter(privkey, &chaincfg.TestNet3Params, - sphinx.NewMemoryReplayLog()) + replay_log := sphinx.NewMemoryReplayLog() + s := sphinx.NewRouter(privkey, &chaincfg.TestNet3Params, replay_log) + + replay_log.Start() + defer replay_log.Stop() var packet sphinx.OnionPacket err = packet.Decode(bytes.NewBuffer(binMsg)) From f2ff78bf7568f0141e2f31ae3d548f0e6e08c641 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 13 May 2019 20:34:48 -0700 Subject: [PATCH 08/18] sphinx: move HopData to path.go re-add its Decode method In this commit, we move the HopData struct back into path.go. Along the way, we remove its old HMAC field (since it's now in the hop payload), and also restore its Decode method for later use. --- path.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ sphinx.go | 77 +------------------------------------------------------ 2 files changed, 77 insertions(+), 76 deletions(-) diff --git a/path.go b/path.go index 5ca37aa..e7da965 100644 --- a/path.go +++ b/path.go @@ -21,6 +21,82 @@ const ( NumFramesShift = 4 ) +// 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 { + // 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.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.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 + } + + if _, err := io.ReadFull(r, hd.ExtraBytes[:]); err != nil { + return err + } + + return err +} + // 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 datafield by the onion router, while the Realm is modified to diff --git a/sphinx.go b/sphinx.go index 12a082f..e91660c 100644 --- a/sphinx.go +++ b/sphinx.go @@ -5,7 +5,6 @@ import ( "crypto/ecdsa" "crypto/hmac" "crypto/sha256" - "encoding/binary" "io" "math/big" @@ -124,66 +123,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.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 -} - // generateSharedSecrets by the given nodes pubkeys, generates the shared // secrets. func generateSharedSecrets(paymentPath []*btcec.PublicKey, @@ -276,7 +215,6 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, var ( mixHeader [routingInfoSize]byte nextHmac [HMACSize]byte - hopDataBuf bytes.Buffer hopPayloadBuf bytes.Buffer ) @@ -293,7 +231,6 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, // last hop to recognize that it is the destination for a // particular payment. paymentPath[i].HopPayload.HMAC = nextHmac - paymentPath[i].HopData.HMAC = nextHmac // Next, using the key dedicated for our stream cipher, we'll // generate enough bytes to obfuscate this layer of the onion @@ -306,19 +243,7 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, numFrames := paymentPath[i].HopPayload.NumFrames() rightShift(mixHeader[:], numFrames*FrameSize) - // 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) - if err != nil { - return nil, err - } - - paymentPath[i].HopPayload.Payload = append( - hopDataBuf.Bytes(), paymentPath[i].HopPayload.Payload..., - ) - - err = paymentPath[i].HopPayload.Encode(&hopPayloadBuf) + err := paymentPath[i].HopPayload.Encode(&hopPayloadBuf) if err != nil { return nil, err } From b175ee517727bf6fee975b4dfa2b417e334c0152 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 13 May 2019 20:40:59 -0700 Subject: [PATCH 09/18] sphinx: add new NewHopPayload constructor In this commit, we add a new constructor to abstract away the details of the hop payload framing from the caller. This new function accepts the hop data, as well as optional EOB bytes. We'll then map these two pieces of data into a single hop payload by appending the EOB bytes to the serialized hop data. --- cmd/main.go | 15 +++++++++++--- path.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++++-- path_test.go | 11 +++++++---- sphinx.go | 1 - sphinx_test.go | 38 +++++++++++++++++++++++------------- 5 files changed, 95 insertions(+), 23 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index fca8464..e5a903f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -59,12 +59,21 @@ func parseOnionSpec(spec OnionSpec) (*sphinx.PaymentPath, *btcec.PrivateKey, err path[i].NodePub = *pubkey - path[i].HopPayload.Realm[0] = byte(hop.Realm) - path[i].HopPayload.Payload, err = hex.DecodeString(hop.Payload) + payload, err := hex.DecodeString(hop.Payload) if err != nil { - log.Fatalf("%s is not a valid hex payload %s", hop.Payload, err) + log.Fatalf("%s is not a valid hex payload %s", + hop.Payload, err) } + hopPayload, err := sphinx.NewHopPayload( + byte(hop.Realm), 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 diff --git a/path.go b/path.go index e7da965..5f6cd77 100644 --- a/path.go +++ b/path.go @@ -99,18 +99,67 @@ func (hd *HopData) Decode(r io.Reader) error { // 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 datafield by the onion router, while the Realm is modified to +// opaque data field by the onion router, while the Realm is modified to // indicate how many hops are to be read by the processing node. The 4 MSB in // the realm indicate how many additional hops are to be processed to collect // the entire payload. type HopPayload struct { - Realm [1]byte + // realm denotes the "real" of target chain of the next hop. For + // bitcoin, this value will be 0x00. + realm [RealmByteSize]byte + // 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(realm byte, 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. + if hopData == nil && len(eob) == 0 { + return h, fmt.Errorf("either hop data or eob must " + + "be specified") + } + + // 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 + } + } + + // Finally, we'll write out the EOB portion to the same buffer to + // ensure it comes after mandatory hop payload. + if _, err := b.Write(eob); err != nil { + return h, nil + } + + h.realm = [RealmByteSize]byte{realm} + h.Payload = b.Bytes() + + return h, nil +} + +// Realm returns the context specific representation of the realm for a hop. +func (hp *HopPayload) Realm() byte { + return hp.realm[0] & RealmMaskBytes +} // NumFrames returns the total number of frames it'll take to pack the target // HopPayload into a Sphinx packet. func (hp *HopPayload) NumFrames() int { diff --git a/path_test.go b/path_test.go index fc9ca22..450c19f 100644 --- a/path_test.go +++ b/path_test.go @@ -21,14 +21,17 @@ func TestHopPayloadSizes(t *testing.T) { } for _, tt := range tests { - hp := HopPayload{ - Realm: [1]byte{1}, - Payload: bytes.Repeat([]byte{0x00}, tt.size), + hp, err := NewHopPayload( + 1, nil, bytes.Repeat([]byte{0x00}, tt.size), + ) + if err != nil { + t.Fatalf("unable to make hop payload: %v", err) } actual := hp.NumFrames() if actual != tt.expected { - t.Errorf("Wrong number of hops returned: expected %d, actual %d", tt.expected, actual) + t.Errorf("wrong number of hops returned: expected "+ + "%d, actual %d", tt.expected, actual) } hp.CalculateRealm() diff --git a/sphinx.go b/sphinx.go index e91660c..3350372 100644 --- a/sphinx.go +++ b/sphinx.go @@ -268,7 +268,6 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, packet := append(mixHeader[:], assocData...) nextHmac = calcMac(muKey, packet) - hopDataBuf.Reset() hopPayloadBuf.Reset() } diff --git a/sphinx_test.go b/sphinx_test.go index 761d820..fc89df1 100644 --- a/sphinx_test.go +++ b/sphinx_test.go @@ -120,23 +120,31 @@ func newTestVarSizeRoute(numHops int, extraPayloadSize []int) ([]*Router, *Payme copy(hopData.NextAddress[:], bytes.Repeat([]byte{byte(i)}, 8)) + var extraData []byte + + // If we were told to increase the payload with some extra data + // do it now. + if extraPayloadSize[i] > 0 { + extraData = bytes.Repeat([]byte{'A'}, extraPayloadSize[i]) + } + + hopPayload, err := NewHopPayload(0, &hopData, extraData) + 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, } } - // If we were told to increase the payload with some extra data do it - // now: for i := 0; i < route.TrueRouteLength(); i++ { - if extraPayloadSize[i] > 0 { - extra := bytes.Repeat([]byte{'A'}, extraPayloadSize[i]) - route[i].HopPayload.Payload = append(route[i].HopPayload.Payload, extra...) - } } // 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), @@ -172,18 +180,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(0, &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, } } @@ -548,7 +560,7 @@ func TestMultiFrameEncodeDecode(t *testing.T) { t.Logf("Processing at hop: %v \n", i) onionPacket, err := hop.ProcessOnionPacket(fwdMsg, nil, uint32(i)+1) if err != nil { - t.Fatalf("Node %v was unable to process the "+ + t.Fatalf("node %v was unable to process the "+ "forwarding message: %v", i, err) } From d70c20fbce05d205fc44c0cf1abc93b23bfd883c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 13 May 2019 20:42:48 -0700 Subject: [PATCH 10/18] sphinx: get rid of mutating CalculateRealm method in favor of packRealm Rather than mutate the underlying realm byte, we'll now expose two methods to the caller: one for the "external" realm, and one of the realm that we'll encode in the payload. --- path.go | 42 +++++++++++++++++++++++++----------------- path_test.go | 7 ++++--- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/path.go b/path.go index 5f6cd77..ca26ff1 100644 --- a/path.go +++ b/path.go @@ -160,6 +160,19 @@ func NewHopPayload(realm byte, hopData *HopData, eob []byte) (HopPayload, error) func (hp *HopPayload) Realm() byte { return hp.realm[0] & RealmMaskBytes } + +// payloadRealm returns the realm that will be used within the raw packed hop +// payload. This differs from the Realm method above in that it uses space to +// encode the packet type and number of frames. The final encoding uses the +// first 4 bits of the realm to encode the number of frames used, and the +// latter 4 bits to encode the real realm type. +func (hp *HopPayload) payloadRealm() byte { + maskedRealm := hp.realm[0] & 0x0F + numFrames := hp.NumFrames() + + return maskedRealm | (byte(numFrames-1) << NumFramesShift) +} + // NumFrames returns the total number of frames it'll take to pack the target // HopPayload into a Sphinx packet. func (hp *HopPayload) NumFrames() int { @@ -177,34 +190,29 @@ func (hp *HopPayload) NumFrames() int { return 2 + int(math.Ceil(float64(remainder)/65)) } -// CalculateRealm computes the proper realm encoding in place. The final -// encoding uses the first 4 bits of the realm to encode the number of frames -// used, and the latter 4 bits to encode the real realm type. -func (hp *HopPayload) CalculateRealm() { - maskedRealm := hp.Realm[0] & 0x0F - numFrames := hp.NumFrames() +// packRealm writes out the proper realm encoding in place to the target +// io.Writer. +func (hp *HopPayload) packRealm(w io.Writer) error { + realm := hp.payloadRealm() + if _, err := w.Write([]byte{realm}); err != nil { + return err + } - hp.Realm[0] = maskedRealm | (byte(numFrames-1) << NumFramesShift) + return nil } // Encode encodes the hop payload into the passed writer. func (hp *HopPayload) Encode(w io.Writer) error { // We'll need to add enough padding bytes to position the HMAC at the // end of the payload - padding := hp.NumFrames()*65 - len(hp.Payload) - 1 - 32 + padding := hp.NumFrames()*FrameSize - len(hp.Payload) - 1 - HMACSize if padding < 0 { return fmt.Errorf("cannot have negative padding: %v", padding) } - // Before we write the realm out, we need to calculate the current - // realm based on the "true" realm as well as the number of frames it - // takes to compute the hop payload. - hp.CalculateRealm() - - if _, err := w.Write(hp.Realm[:]); err != nil { + if err := hp.packRealm(w); err != nil { return err } - if _, err := w.Write(hp.Payload); err != nil { return err } @@ -228,11 +236,11 @@ func (hp *HopPayload) Encode(w io.Writer) error { // Decode unpacks an encoded HopPayload from the passed reader into the target // HopPayload. func (hp *HopPayload) Decode(r io.Reader) error { - if _, err := io.ReadFull(r, hp.Realm[:]); err != nil { + if _, err := io.ReadFull(r, hp.realm[:]); err != nil { return err } - numFrames := int(hp.Realm[0]>>NumFramesShift) + 1 + numFrames := int(hp.realm[0]>>NumFramesShift) + 1 numBytes := (numFrames * FrameSize) - 32 - 1 hp.Payload = make([]byte, numBytes) diff --git a/path_test.go b/path_test.go index 450c19f..ce018c1 100644 --- a/path_test.go +++ b/path_test.go @@ -34,9 +34,10 @@ func TestHopPayloadSizes(t *testing.T) { "%d, actual %d", tt.expected, actual) } - hp.CalculateRealm() - if hp.Realm[0] != tt.realm { - t.Errorf("Updated realm did not match our expectation: expected %q, actual %q", tt.realm, hp.Realm) + if hp.payloadRealm() != tt.realm { + t.Errorf("payload realm did not match our "+ + "expectation: expected %q, actual %q", tt.realm, + hp.Realm()) } } } From 1ce3fd76aa392a0d0ee9a4b1ffe1274324c7959e Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 13 May 2019 20:45:16 -0700 Subject: [PATCH 11/18] sphinx: remove HopData from OnionHop, expose left over EOB from payload In this commit, we remove the HopData field from the OnionHop struct as it's no longer needed since we can obtain the HopData via the HopData() method of the HopPayload struct. Additinoally, we also now return the set of unused payload bytes after the HopData encoding, we call these the EOB or extra onion blobs. With this distinction we now provide the caller of ProcessOnionPacket with the forwarding instructions, then the left over EOB bytes. These EOB bytes will then be interpreted using a TLV format or any other arbitrary framing system. --- bench_test.go | 10 +++++++--- path.go | 43 +++++++++++++++---------------------------- sphinx.go | 15 +++++++++------ sphinx_test.go | 26 +++++++++++++++----------- 4 files changed, 46 insertions(+), 48 deletions(-) diff --git a/bench_test.go b/bench_test.go index cb345ad..0d64a3a 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(0, &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/path.go b/path.go index ca26ff1..ec02f3e 100644 --- a/path.go +++ b/path.go @@ -257,39 +257,30 @@ func (hp *HopPayload) Decode(r io.Reader) error { // 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. -func (hp *HopPayload) HopData() (*HopData, error) { +// 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, []byte, error) { // If this isn't the "base" realm, then we can't extract the expected // hop payload structure from the payload. - if hp.Realm[0]&RealmMaskBytes != 0x00 { - return nil, fmt.Errorf("payload is not a HopData payload, "+ - "realm=%d", hp.Realm[0]) + if hp.Realm() != 0x00 { + return nil, nil, fmt.Errorf("payload is not a HopData "+ + "payload, realm=%d", hp.Realm()) } // Now that we know the payload has the structure we expect, we'll // decode the payload into the HopData. - hd := HopData{ - Realm: [1]byte{hp.Realm[0] & RealmMaskBytes}, - HMAC: hp.HMAC, + var hd HopData + payloadReader := bytes.NewBuffer(hp.Payload) + if err := hd.Decode(payloadReader); err != nil { + return &hd, nil, nil } - r := bytes.NewBuffer(hp.Payload) - if _, err := io.ReadFull(r, hd.NextAddress[:]); err != nil { - return nil, err - } - - if err := binary.Read(r, binary.BigEndian, &hd.ForwardAmount); err != nil { - return nil, err - } - - if err := binary.Read(r, binary.BigEndian, &hd.OutgoingCltv); err != nil { - return nil, err - } + // What's left over in the buffer that wasn't parsed as part of the + // forwarding instructions is our lingering EOB. + eob := payloadReader.Bytes() - if _, err := io.ReadFull(r, hd.ExtraBytes[:]); err != nil { - return nil, err - } - - return &hd, nil + return &hd, eob, nil } // NumMaxHops is the maximum path length. This should be set to an estimate of @@ -318,10 +309,6 @@ 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 diff --git a/sphinx.go b/sphinx.go index 3350372..c592caf 100644 --- a/sphinx.go +++ b/sphinx.go @@ -443,10 +443,10 @@ type ProcessedPacket struct { // MoreHops. ForwardingInstructions *HopData - // RawPayload is the raw (plaintext) payload that was passed to the - // processing node in the onion packet. It provides accessors to get - // the parsed and interpreted data. - RawPayload HopPayload + // ExtraOnionBlob is the raw EOB payload unpacked by this hop. This is + // the portion of the payload _without_ the prefixed forwarding + // instructions. + ExtraOnionBlob []byte // NextPacket is the onion packet that should be forwarded to the next // hop as denoted by the ForwardingInstructions field. @@ -454,6 +454,8 @@ type ProcessedPacket struct { // NOTE: This field will only be populated iff the above Action is // MoreHops. NextPacket *OnionPacket + + rawPayload HopPayload } // Router is an onion router within the Sphinx network. The router is capable @@ -649,7 +651,7 @@ func processOnionPacket(onionPkt *OnionPacket, sharedSecret *Hash256, action = ExitNode } - hopData, err := outerHopPayload.HopData() + hopData, eob, err := outerHopPayload.HopData() if err != nil { return nil, err } @@ -660,8 +662,9 @@ func processOnionPacket(onionPkt *OnionPacket, sharedSecret *Hash256, return &ProcessedPacket{ Action: action, ForwardingInstructions: hopData, - RawPayload: *outerHopPayload, + ExtraOnionBlob: eob, NextPacket: innerPkt, + rawPayload: *outerHopPayload, }, nil } diff --git a/sphinx_test.go b/sphinx_test.go index fc89df1..2fa85cc 100644 --- a/sphinx_test.go +++ b/sphinx_test.go @@ -113,11 +113,9 @@ func newTestVarSizeRoute(numHops int, extraPayloadSize []int) ([]*Router, *Payme ) 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)) var extraData []byte @@ -157,7 +155,13 @@ func newTestVarSizeRoute(numHops int, extraPayloadSize []int) ([]*Router, *Payme 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 @@ -564,22 +568,22 @@ func TestMultiFrameEncodeDecode(t *testing.T) { "forwarding message: %v", i, err) } - // Check that the framecount matches what we expect - frameCount := onionPacket.RawPayload.NumFrames() + // Check that the frame count matches what we expect + frameCount := onionPacket.rawPayload.NumFrames() if tt.expectedFrames[i] != frameCount { - t.Fatalf("Incorrect number of payload frames: expected %d, got %d", - tt.expectedFrames[i], - frameCount, + t.Fatalf("incorrect number of payload "+ + "frames: expected %d, got %d", + tt.expectedFrames[i], frameCount, ) } // Check that the payload contents are identical expected := path[i].HopPayload if !bytes.Equal(path[i].HopPayload.Payload, expected.Payload) { - t.Fatalf("Processing error, hop-payload parsed incorrectly."+ - " expected %x, got %x", + t.Fatalf("processing error, hop-payload "+ + "parsed incorrectly. expected %x, got %x", expected.Payload, - onionPacket.RawPayload.Payload) + onionPacket.rawPayload.Payload) } fwdMsg = onionPacket.NextPacket From 60a2703c2d381956c01d675f843d6cfa59f81708 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 3 Jul 2019 18:34:53 -0700 Subject: [PATCH 12/18] sphinx: convert to variable sized EOB payloads In this commit, we make the jump from the prior fixed sized frame payloads, to the latest variable sized format. The main change is that now we no longer need to pack all the payloads into a fixed frame size, adding padding. Instead, for the new TLV format, we'll now use a var-int to signal the length of the payload. In the process, we lift the Realm field from the HopPayload struct into the HopData struct. We do this as the new TLV payload has no realm field, and it's instead used to signal the existence of the legacy payload format. --- bench_test.go | 2 +- path.go | 249 +++++++++++++++++++++++++++---------------------- path_test.go | 43 --------- sphinx.go | 60 +++++------- sphinx_test.go | 6 +- 5 files changed, 163 insertions(+), 197 deletions(-) delete mode 100644 path_test.go diff --git a/bench_test.go b/bench_test.go index 0d64a3a..ea4e86e 100644 --- a/bench_test.go +++ b/bench_test.go @@ -33,7 +33,7 @@ func BenchmarkPathPacketConstruction(b *testing.B) { } copy(hopData.NextAddress[:], bytes.Repeat([]byte{byte(i)}, 8)) - hopPayload, err := NewHopPayload(0, &hopData, nil) + hopPayload, err := NewHopPayload(&hopData, nil) if err != nil { b.Fatalf("unable to create new hop payload: %v", err) } diff --git a/path.go b/path.go index ec02f3e..e16f4a2 100644 --- a/path.go +++ b/path.go @@ -1,24 +1,14 @@ package sphinx import ( + "bufio" "bytes" "encoding/binary" "fmt" "io" - "math" "github.com/btcsuite/btcd/btcec" -) - -const ( - // RealmMaskBytes is the mask to apply the realm in order to pack or - // decode the 4 LSB of the realm field. - RealmMaskBytes = 0x0f - - // NumFramesShift is the number of bytes to shift the encoding of the - // number of frames by in order to pack/unpack them into the 4 MSB bits - // of the realm field. - NumFramesShift = 4 + "github.com/btcsuite/btcd/wire" ) // HopData is the information destined for individual hops. It is a fixed size @@ -28,6 +18,10 @@ const ( // 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 @@ -54,6 +48,10 @@ type HopData struct { // 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 } @@ -76,6 +74,10 @@ func (hd *HopData) Encode(w io.Writer) error { // 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 } @@ -90,23 +92,34 @@ func (hd *HopData) Decode(r io.Reader) error { return err } - if _, err := io.ReadFull(r, hd.ExtraBytes[:]); 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, while the Realm is modified to -// indicate how many hops are to be read by the processing node. The 4 MSB in -// the realm indicate how many additional hops are to be processed to collect -// the entire payload. +// opaque data field by the onion router. The included Type field informs the +// serialization/deserialziation of the raw payload. type HopPayload struct { - // realm denotes the "real" of target chain of the next hop. For - // bitcoin, this value will be 0x00. - realm [RealmByteSize]byte + // 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 @@ -122,8 +135,7 @@ type HopPayload struct { // 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(realm byte, hopData *HopData, eob []byte) (HopPayload, error) { - +func NewHopPayload(hopData *HopData, eob []byte) (HopPayload, error) { var ( h HopPayload b bytes.Buffer @@ -131,9 +143,14 @@ func NewHopPayload(realm byte, hopData *HopData, eob []byte) (HopPayload, error) // We can't proceed if neither the hop data or the EOB has been // specified by the caller. - if hopData == nil && len(eob) == 0 { + 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 @@ -142,90 +159,67 @@ func NewHopPayload(realm byte, hopData *HopData, eob []byte) (HopPayload, error) if err := hopData.Encode(&b); err != nil { return h, nil } - } - // Finally, we'll write out the EOB portion to the same buffer to - // ensure it comes after mandatory hop payload. - if _, err := b.Write(eob); 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.realm = [RealmByteSize]byte{realm} h.Payload = b.Bytes() return h, nil } -// Realm returns the context specific representation of the realm for a hop. -func (hp *HopPayload) Realm() byte { - return hp.realm[0] & RealmMaskBytes -} - -// payloadRealm returns the realm that will be used within the raw packed hop -// payload. This differs from the Realm method above in that it uses space to -// encode the packet type and number of frames. The final encoding uses the -// first 4 bits of the realm to encode the number of frames used, and the -// latter 4 bits to encode the real realm type. -func (hp *HopPayload) payloadRealm() byte { - maskedRealm := hp.realm[0] & 0x0F - numFrames := hp.NumFrames() - - return maskedRealm | (byte(numFrames-1) << NumFramesShift) -} - -// NumFrames returns the total number of frames it'll take to pack the target -// HopPayload into a Sphinx packet. -func (hp *HopPayload) NumFrames() int { - // If it all fits in the legacy payload size, don't use any additional - // frames. - if len(hp.Payload) <= 32 { - return 1 - } - - // Otherwise we'll need at least one additional frame: subtract the 64 - // bytes we can stuff into payload and hmac of the first, and the 33 - // bytes we can pack into the payload of the second, then divide the - // remainder by 65. - remainder := len(hp.Payload) - 64 - 33 - return 2 + int(math.Ceil(float64(remainder)/65)) -} - -// packRealm writes out the proper realm encoding in place to the target -// io.Writer. -func (hp *HopPayload) packRealm(w io.Writer) error { - realm := hp.payloadRealm() - if _, err := w.Write([]byte{realm}); err != nil { - return err +// 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 nil + return size } // Encode encodes the hop payload into the passed writer. func (hp *HopPayload) Encode(w io.Writer) error { - // We'll need to add enough padding bytes to position the HMAC at the - // end of the payload - padding := hp.NumFrames()*FrameSize - len(hp.Payload) - 1 - HMACSize - if padding < 0 { - return fmt.Errorf("cannot have negative padding: %v", padding) - } + switch hp.Type { - if err := hp.packRealm(w); err != nil { - return err - } - if _, err := w.Write(hp.Payload); err != nil { - return err - } + // 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 - // If we need to pad out the frame at all, then we'll do so now before - // we write out the HMAC. - if padding > 0 { - _, err := w.Write(bytes.Repeat([]byte{0x00}, padding)) + // For the TLV payload, we'll first prepend the length of the payload + // as a var-int. + case PayloadTLV: + err := wire.WriteVarInt(w, 0, uint64(len(hp.Payload))) 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 } @@ -236,19 +230,48 @@ func (hp *HopPayload) Encode(w io.Writer) error { // Decode unpacks an encoded HopPayload from the passed reader into the target // HopPayload. func (hp *HopPayload) Decode(r io.Reader) error { - if _, err := io.ReadFull(r, hp.realm[:]); err != nil { + 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 } - numFrames := int(hp.realm[0]>>NumFramesShift) + 1 - numBytes := (numFrames * FrameSize) - 32 - 1 + 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. + varInt, err := wire.ReadVarInt(bufReader, 0) + if err != nil { + return err + } - hp.Payload = make([]byte, numBytes) - if _, err := io.ReadFull(r, hp.Payload[:]); err != nil { - return err + payloadSize = uint32(varInt) + hp.Type = PayloadTLV } - if _, err := io.ReadFull(r, hp.HMAC[:]); err != nil { + // 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 } @@ -260,31 +283,29 @@ func (hp *HopPayload) Decode(r io.Reader) error { // 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, []byte, error) { +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.Realm() != 0x00 { - return nil, nil, fmt.Errorf("payload is not a HopData "+ - "payload, realm=%d", hp.Realm()) + 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 - payloadReader := bytes.NewBuffer(hp.Payload) if err := hd.Decode(payloadReader); err != nil { - return &hd, nil, nil + return nil, err } - // What's left over in the buffer that wasn't parsed as part of the - // forwarding instructions is our lingering EOB. - eob := payloadReader.Bytes() - - return &hd, eob, nil + 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 @@ -352,17 +373,17 @@ func (p *PaymentPath) TrueRouteLength() int { return routeLength } -// TotalFrames returns the total numebr of frames that it'll take to create a -// Sphinx packet from the target PaymentPath. -func (p *PaymentPath) TotalFrames() int { - var frameCount int +// 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() { - break + continue } - frameCount += hop.HopPayload.NumFrames() + totalSize += hop.HopPayload.NumBytes() } - return frameCount + return totalSize } diff --git a/path_test.go b/path_test.go deleted file mode 100644 index ce018c1..0000000 --- a/path_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package sphinx - -import ( - "bytes" - "testing" -) - -func TestHopPayloadSizes(t *testing.T) { - var tests = []struct { - size int - expected int - realm byte - }{ - {30, 1, 0x01}, - {32, 1, 0x01}, - {33, 2, 0x11}, - {97, 2, 0x11}, // The largest possible 2-hop payload - {98, 3, 0x21}, - {162, 3, 0x21}, - {163, 4, 0x31}, - } - - for _, tt := range tests { - hp, err := NewHopPayload( - 1, nil, bytes.Repeat([]byte{0x00}, tt.size), - ) - if err != nil { - t.Fatalf("unable to make hop payload: %v", err) - } - - actual := hp.NumFrames() - if actual != tt.expected { - t.Errorf("wrong number of hops returned: expected "+ - "%d, actual %d", tt.expected, actual) - } - - if hp.payloadRealm() != tt.realm { - t.Errorf("payload realm did not match our "+ - "expectation: expected %q, actual %q", tt.realm, - hp.Realm()) - } - } -} diff --git a/sphinx.go b/sphinx.go index c592caf..b01c404 100644 --- a/sphinx.go +++ b/sphinx.go @@ -43,24 +43,12 @@ const ( HopDataSize = (RealmByteSize + AddressSize + AmtForwardSize + OutgoingCLTVSize + NumPaddingBytes + HMACSize) - // FramSize is the size of a frame in the packet. A frame is a region - // of contiguous memory in the onion that can be used to store a - // payload in. Each hop may use a discrete number of frames for its - // payload. There are two parts of the payload that are fixed: a) the - // first byte in the first frame is the realm-byte, which tells us how - // many frames the current payload uses and how the payload should be - // parsed, and b) the last 32 bytes of the last frame are the HMAC that - // should be passed to the next hop, or 0 in case of the last hop. - // Bytes between these two points can be freely used to store the - // actual payload. - FrameSize = 65 - // 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 * FrameSize + MaxPayloadSize = NumMaxHops * HopDataSize // sharedSecretSize is the size in bytes of the shared secrets. sharedSecretSize = 32 @@ -70,14 +58,14 @@ const ( // each hop of the route, the first pair in cleartext and the following // pairs increasingly obfuscated. In case fewer than numMaxHops are // used, then the remainder is padded with null-bytes, also obfuscated. - routingInfoSize = NumMaxHops * FrameSize + routingInfoSize = NumMaxHops * HopDataSize // 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 maxPayloadSize bytes at the end are used to + // 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 + MaxPayloadSize + 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 @@ -237,13 +225,15 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey, // packet. streamBytes := generateCipherStream(rhoKey, routingInfoSize) + payload := paymentPath[i].HopPayload + // Before we assemble the packet, we'll shift the current // mix-header to the right in order to make room for this next // per-hop data. - numFrames := paymentPath[i].HopPayload.NumFrames() - rightShift(mixHeader[:], numFrames*FrameSize) + shiftSize := payload.NumBytes() + rightShift(mixHeader[:], shiftSize) - err := paymentPath[i].HopPayload.Encode(&hopPayloadBuf) + err := payload.Encode(&hopPayloadBuf) if err != nil { return nil, err } @@ -307,26 +297,27 @@ func generateHeaderPadding(key string, path *PaymentPath, sharedSecrets []Hash25 // We have to generate a filler that matches all but the last hop (the // last hop won't generate an HMAC) - fillerFrames := path.TotalFrames() - path[numHops-1].HopPayload.NumFrames() - filler := make([]byte, fillerFrames*FrameSize) + 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 + // Sum up how many frames were used by prior hops. fillerStart := routingInfoSize for _, p := range path[:i] { - fillerStart = fillerStart - (p.HopPayload.NumFrames() * FrameSize) + 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.NumFrames() * FrameSize) + fillerEnd := routingInfoSize + path[i].HopPayload.NumBytes() streamKey := generateKey(key, &sharedSecrets[i]) streamBytes := generateCipherStream(streamKey, numStreamBytes) xor(filler, filler, streamBytes[fillerStart:fillerEnd]) } + return filler } @@ -443,10 +434,11 @@ type ProcessedPacket struct { // MoreHops. ForwardingInstructions *HopData - // ExtraOnionBlob is the raw EOB payload unpacked by this hop. This is - // the portion of the payload _without_ the prefixed forwarding - // instructions. - ExtraOnionBlob []byte + // 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. @@ -454,8 +446,6 @@ type ProcessedPacket struct { // NOTE: This field will only be populated iff the above Action is // MoreHops. NextPacket *OnionPacket - - rawPayload HopPayload } // Router is an onion router within the Sphinx network. The router is capable @@ -586,8 +576,7 @@ 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}, MaxPayloadSize) headerWithPadding := append(routeInfo[:], zeroBytes...) @@ -611,7 +600,7 @@ func unwrapPacket(onionPkt *OnionPacket, sharedSecret *Hash256, // 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[hopPayload.NumFrames()*FrameSize:]) + copy(nextMixHeader[:], hopInfo[hopPayload.NumBytes():]) innerPkt := &OnionPacket{ Version: onionPkt.Version, EphemeralKey: nextDHKey, @@ -651,7 +640,7 @@ func processOnionPacket(onionPkt *OnionPacket, sharedSecret *Hash256, action = ExitNode } - hopData, eob, err := outerHopPayload.HopData() + hopData, err := outerHopPayload.HopData() if err != nil { return nil, err } @@ -662,9 +651,8 @@ func processOnionPacket(onionPkt *OnionPacket, sharedSecret *Hash256, return &ProcessedPacket{ Action: action, ForwardingInstructions: hopData, - ExtraOnionBlob: eob, + Payload: *outerHopPayload, NextPacket: innerPkt, - rawPayload: *outerHopPayload, }, nil } diff --git a/sphinx_test.go b/sphinx_test.go index 2fa85cc..0ddb2b9 100644 --- a/sphinx_test.go +++ b/sphinx_test.go @@ -126,7 +126,7 @@ func newTestVarSizeRoute(numHops int, extraPayloadSize []int) ([]*Router, *Payme extraData = bytes.Repeat([]byte{'A'}, extraPayloadSize[i]) } - hopPayload, err := NewHopPayload(0, &hopData, extraData) + hopPayload, err := NewHopPayload(&hopData, nil) if err != nil { return nil, nil, nil, nil, fmt.Errorf("unable to "+ "create new hop payload: %v", err) @@ -155,7 +155,7 @@ func newTestVarSizeRoute(numHops int, extraPayloadSize []int) ([]*Router, *Payme var hopsData []HopData for i := 0; i < len(nodes); i++ { - hopData, _, err := route[i].HopPayload.HopData() + hopData, err := route[i].HopPayload.HopData() if err != nil { return nil, nil, nil, nil, fmt.Errorf("unable to "+ "gen hop data: %v", err) @@ -190,7 +190,7 @@ func TestBolt4Packet(t *testing.T) { copy(hopData.NextAddress[:], bytes.Repeat([]byte{byte(i)}, 8)) hopsData = append(hopsData, hopData) - hopPayload, err := NewHopPayload(0, &hopData, nil) + hopPayload, err := NewHopPayload(&hopData, nil) if err != nil { t.Fatalf("unable to make hop payload: %v", err) } From 7f17f54846956ac3290540e8ce10be9aaa26647c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 3 Jul 2019 18:36:00 -0700 Subject: [PATCH 13/18] sphinx: replace prior EOB test with TestSphinxHopVariableSizedPayloads We no longer have the concept of frames, so the old test no longer applies. --- sphinx_test.go | 301 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 242 insertions(+), 59 deletions(-) diff --git a/sphinx_test.go b/sphinx_test.go index 0ddb2b9..a61018d 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" @@ -87,11 +89,6 @@ var ( ) func newTestRoute(numHops int) ([]*Router, *PaymentPath, *[]HopData, *OnionPacket, error) { - extraPayloadSize := make([]int, numHops) - return newTestVarSizeRoute(numHops, extraPayloadSize) -} - -func newTestVarSizeRoute(numHops int, extraPayloadSize []int) ([]*Router, *PaymentPath, *[]HopData, *OnionPacket, error) { nodes := make([]*Router, numHops) // Create numHops random sphinx nodes. @@ -118,14 +115,6 @@ func newTestVarSizeRoute(numHops int, extraPayloadSize []int) ([]*Router, *Payme } copy(hopData.NextAddress[:], bytes.Repeat([]byte{byte(i)}, 8)) - var extraData []byte - - // If we were told to increase the payload with some extra data - // do it now. - if extraPayloadSize[i] > 0 { - extraData = bytes.Repeat([]byte{'A'}, extraPayloadSize[i]) - } - hopPayload, err := NewHopPayload(&hopData, nil) if err != nil { return nil, nil, nil, nil, fmt.Errorf("unable to "+ @@ -138,9 +127,6 @@ func newTestVarSizeRoute(numHops int, extraPayloadSize []int) ([]*Router, *Payme } } - for i := 0; i < route.TrueRouteLength(); i++ { - } - // Generate a forwarding message to route to the final node via the // generated intermediate nodes above. Destination should be Hash160, // adding padding so parsing still works. @@ -533,61 +519,258 @@ func TestSphinxEncodeDecode(t *testing.T) { } } -// Create an onion with 5 hops, and the 3rd hop -func TestMultiFrameEncodeDecode(t *testing.T) { +func newEOBRoute(numHops uint32, + eobMapping map[int]HopPayload) (*OnionPacket, []*Router, error) { - var payloadtests = []struct { - extraPayload []int - expectedFrames []int - }{ - {[]int{0, 0, 0, 0, 0}, []int{1, 1, 1, 1, 1}}, - {[]int{60, 0, 0, 0, 0}, []int{2, 1, 1, 1, 1}}, - {[]int{0, 0, 0, 0, 60}, []int{1, 1, 1, 1, 2}}, - {[]int{0, 0, 0, 0, 130}, []int{1, 1, 1, 1, 3}}, - {[]int{0, 0, 60, 0, 0}, []int{1, 1, 2, 1, 1}}, + 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) } - for _, tt := range payloadtests { - t.Run(fmt.Sprintf("%v", tt), func(t *testing.T) { - nodes, path, _, fwdMsg, err := newTestVarSizeRoute(5, tt.extraPayload) + 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("unable to create random onion packet: %v", err) + t.Fatalf("#%v: unable to process packet at "+ + "hop #%v: %v", testCaseNum, i, err) } - for i := 0; i < len(nodes); i++ { - // Start each node's ReplayLog and defer shutdown - nodes[i].log.Start() - defer nodes[i].log.Stop() + // 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)) + } - hop := nodes[i] + if eobData.Type != processedPacket.Payload.Type { + t.Fatalf("mismatched types: expected %v "+ + "got %v", eobData.Type, + processedPacket.Payload.Type) + } - t.Logf("Processing at hop: %v \n", i) - onionPacket, err := hop.ProcessOnionPacket(fwdMsg, nil, uint32(i)+1) - if err != nil { - t.Fatalf("node %v was unable to process the "+ - "forwarding message: %v", i, err) + // 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 + } - // Check that the frame count matches what we expect - frameCount := onionPacket.rawPayload.NumFrames() - if tt.expectedFrames[i] != frameCount { - t.Fatalf("incorrect number of payload "+ - "frames: expected %d, got %d", - tt.expectedFrames[i], frameCount, - ) - } + // 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) + } - // Check that the payload contents are identical - expected := path[i].HopPayload - if !bytes.Equal(path[i].HopPayload.Payload, expected.Payload) { - t.Fatalf("processing error, hop-payload "+ - "parsed incorrectly. expected %x, got %x", - expected.Payload, - onionPacket.rawPayload.Payload) + // 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)) } - - fwdMsg = onionPacket.NextPacket } - }) + + nextPkt = processedPacket.NextPacket + } + } +} } } From 41ff4a6f3ec438c53a70b2a5106c80d3ae51225e Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 3 Jul 2019 18:36:43 -0700 Subject: [PATCH 14/18] sphinx: add variable sized EOB spec JSON tests --- sphinx_test.go | 138 +++++++++++++++++++++++++++ testdata/onion-test-multi-frame.json | 42 ++++++++ 2 files changed, 180 insertions(+) create mode 100644 testdata/onion-test-multi-frame.json diff --git a/sphinx_test.go b/sphinx_test.go index a61018d..37c1d50 100644 --- a/sphinx_test.go +++ b/sphinx_test.go @@ -772,5 +772,143 @@ func TestSphinxHopVariableSizedPayloads(t *testing.T) { } } } + +// 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..4610a1e --- /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": "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a7181924e4b6c645f2b6eecc821084ec9b16dfb9d2e7622d4c14db4fc5ecdfc07eac50f7d61ab590531cf08000178a333a347f8b4072e7d3ae44f4f309e150b49886d3f044cd6462be389c830784aba767682923c8683404aaf9a8e8d7178977d7094a1ae549f89338c0777551f874159eb42d3a59fb9285ad4e24883f27de23942ec966611e99bee1cee503455be9e8e642cef6cef7b9864130f692283f8a973d47a8f1c1726b6e59969385975c766e35737c8d76388b64f748ee7943ffb0e2ee45c57a1abc40762ae598723d21bd184e2b338f68ebff47219357bd19cd7e01e2337b806ef4d717888e129e59cd3dc31e6201ccb2fd6d7499836f37a993262468bcb3a4dcd03a22818aca49c6b7b9b8e9e870045631d8e039b066ff86e0d1b7291f71cefa7264c70404a8e538b566c17ccc5feab231401e6c08a01bd5edfc1aa8e3e533b96e82d1f91118d508924b923531929aea889fcdf050597c681185f336b1da63b0939aa2b7c50b21b5eb7b6ad66c81fab98a3cdf73f658149e7e9ced4edde5d38c9b8f92e16f6b4ab13d7fca6a0e4ecc9f9de611a90da6e99c39551094c56e3196f282c5dffd9fc4b2fc12f3bca8e6fe47eb45fbdd3be21a8a8d200797eae3c9a0497132f92410d804977408494dff49dd3d8bce248e0b74fd9e6f0f7102c25ddfa02bd9ad9f746abbfa337ef811d5345a9e16b60de1767b209645ba40bd1f9a5f75bc04feca9b27c5554be4fe83fac2cb83aa447a817bb85ae966c68b420063833fada375e2f515965e687a45699632902672c654d1d18d7bcbf55e8fa57f63f2da449f8e1e606e8722df081e5f193fc4179feb99ad22819afdeef211f7c54afdba92aeef0c00b7bc2b65a4813c01f907a8377585708f2d4c940a25328e585714c8ded0a9a4d7a6de1027c1cb7a0198cd3db68b58c0704dfd0cfbe624e9cd18cc0ae5d96697bb476708b9ee0403d211e64e0d5a7683a7a9a140c02f0ff1c6e67a302941b4052bdea8a63e70a3ad62c5b89c698f1fd3c7685cb49705096cad702d02d93bcb1c27a409f4c9bddec001205ca4a2740f19b50900be81c7e847f1a863deea8d35701f1355cad8db57b1d4eb2ab4e29587734785abfb46ddede71928213d7d089dfdeda052827f459f1688cc0935bd47e7bcec27427c8376dcce7e22699567c0d145f8a7db33f6758815f1f15f9f7a9760dec4f34ae095edda4c64e9735bdd029c4e32c2ee31ba47ec5e6bdb97813d52dbd15b4e0b7a2c7f790ae64104d99f38c127f0a093288fa34144adb16b8968d4fa7656fcec99de8503dd46d3b03620a71c7cd085364abd30dccf7fbda25a1cdc102600149c9af1c97aa0372cd2e1909f28ac5c686f432b310e79528c9b8b9e8f314c1e74621ce6308ad2278b81d460892e0d9dd38b7c76d58be6dfd10ae7583ee1e7ef5b3f6f78dc60af0950df1b00cc55b6d178ba2e476bea0eaeef49323b83f05804159e7aef4eed4cc60dd07be76f067dfd0bcfb0b806b69ba921336a20c43c832d0cab8fa3ddeb29e3bf07b0d98a112eb07802756235a49d44a8b82a950d84e95e01971f0e106ccb337f07384e21620e0ad39e16ed9edca123226cf55ac44f449eeb53e38a7f27d101806e4823e4efcc887414240ee6826c4a5cb1c6443ad36ebf905a435c1d9054e54173911b17b5b40f60b3d9fd5f12eac54ca1e20191f5f18544d5fd3d665e9bcef96fb44b76110aa64d9db4c86c9513cbdad546538e8aec521fbe83ceac5e74a15629f1ed0b870a1d0d1e5680b6d6100d1bdd9a2ba3be3772f71854cb12ae9b3afd87cda738dcd107fe56a15f1877450cd0e", + "decode": [ + "4141414141414141414141414141414141414141414141414141414141414141", + "4242424242424242424242424242424242424242424242424242424242424242", + "4343434343434343434343434343434343434343434343434343434343434343", + "4444444444444444444444444444444444444444444444444444444444444444", + "4545454545454545454545454545454545454545454545454545454545454545" + ] +} From 3b4e296d7adffeb239de460f75a22e6e305515f2 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 3 Jul 2019 18:38:45 -0700 Subject: [PATCH 15/18] cmd: update CLI tool with latest API changes --- cmd/main.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index e5a903f..183db99 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -65,9 +65,7 @@ func parseOnionSpec(spec OnionSpec) (*sphinx.PaymentPath, *btcec.PrivateKey, err hop.Payload, err) } - hopPayload, err := sphinx.NewHopPayload( - byte(hop.Realm), nil, payload, - ) + hopPayload, err := sphinx.NewHopPayload(nil, payload) if err != nil { log.Fatalf("unable to make payload: %v", err) } @@ -132,11 +130,11 @@ func main() { } privkey, _ := btcec.PrivKeyFromBytes(btcec.S256(), binKey) - replay_log := sphinx.NewMemoryReplayLog() - s := sphinx.NewRouter(privkey, &chaincfg.TestNet3Params, replay_log) + replayLog := sphinx.NewMemoryReplayLog() + s := sphinx.NewRouter(privkey, &chaincfg.TestNet3Params, replayLog) - replay_log.Start() - defer replay_log.Stop() + replayLog.Start() + defer replayLog.Stop() var packet sphinx.OnionPacket err = packet.Decode(bytes.NewBuffer(binMsg)) From 7302ea44c8e25be94f5c7fc9ca3fb7bd57673164 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 23 Jul 2019 17:44:39 -0700 Subject: [PATCH 16/18] sphinx: switch to using BigSize for varints --- path.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/path.go b/path.go index e16f4a2..5ff51f9 100644 --- a/path.go +++ b/path.go @@ -9,6 +9,7 @@ import ( "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 @@ -210,7 +211,8 @@ func (hp *HopPayload) Encode(w io.Writer) error { // For the TLV payload, we'll first prepend the length of the payload // as a var-int. case PayloadTLV: - err := wire.WriteVarInt(w, 0, uint64(len(hp.Payload))) + var b [8]byte + err := tlv.WriteVarInt(w, uint64(len(hp.Payload)), &b) if err != nil { return err } @@ -254,7 +256,8 @@ func (hp *HopPayload) Decode(r io.Reader) error { default: // Otherwise, this is the new TLV based payload type, so we'll // extract the payload length encoded as a var-int. - varInt, err := wire.ReadVarInt(bufReader, 0) + var b [8]byte + varInt, err := tlv.ReadVarInt(bufReader, &b) if err != nil { return err } From 0779eb2416a81478e8fa25075626296175eca3e8 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 23 Jul 2019 17:44:54 -0700 Subject: [PATCH 17/18] testdata: update to latest variable payload onion using BigSize --- testdata/onion-test-multi-frame.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testdata/onion-test-multi-frame.json b/testdata/onion-test-multi-frame.json index 4610a1e..495de9e 100644 --- a/testdata/onion-test-multi-frame.json +++ b/testdata/onion-test-multi-frame.json @@ -31,7 +31,7 @@ } ] }, - "onion": "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a7181924e4b6c645f2b6eecc821084ec9b16dfb9d2e7622d4c14db4fc5ecdfc07eac50f7d61ab590531cf08000178a333a347f8b4072e7d3ae44f4f309e150b49886d3f044cd6462be389c830784aba767682923c8683404aaf9a8e8d7178977d7094a1ae549f89338c0777551f874159eb42d3a59fb9285ad4e24883f27de23942ec966611e99bee1cee503455be9e8e642cef6cef7b9864130f692283f8a973d47a8f1c1726b6e59969385975c766e35737c8d76388b64f748ee7943ffb0e2ee45c57a1abc40762ae598723d21bd184e2b338f68ebff47219357bd19cd7e01e2337b806ef4d717888e129e59cd3dc31e6201ccb2fd6d7499836f37a993262468bcb3a4dcd03a22818aca49c6b7b9b8e9e870045631d8e039b066ff86e0d1b7291f71cefa7264c70404a8e538b566c17ccc5feab231401e6c08a01bd5edfc1aa8e3e533b96e82d1f91118d508924b923531929aea889fcdf050597c681185f336b1da63b0939aa2b7c50b21b5eb7b6ad66c81fab98a3cdf73f658149e7e9ced4edde5d38c9b8f92e16f6b4ab13d7fca6a0e4ecc9f9de611a90da6e99c39551094c56e3196f282c5dffd9fc4b2fc12f3bca8e6fe47eb45fbdd3be21a8a8d200797eae3c9a0497132f92410d804977408494dff49dd3d8bce248e0b74fd9e6f0f7102c25ddfa02bd9ad9f746abbfa337ef811d5345a9e16b60de1767b209645ba40bd1f9a5f75bc04feca9b27c5554be4fe83fac2cb83aa447a817bb85ae966c68b420063833fada375e2f515965e687a45699632902672c654d1d18d7bcbf55e8fa57f63f2da449f8e1e606e8722df081e5f193fc4179feb99ad22819afdeef211f7c54afdba92aeef0c00b7bc2b65a4813c01f907a8377585708f2d4c940a25328e585714c8ded0a9a4d7a6de1027c1cb7a0198cd3db68b58c0704dfd0cfbe624e9cd18cc0ae5d96697bb476708b9ee0403d211e64e0d5a7683a7a9a140c02f0ff1c6e67a302941b4052bdea8a63e70a3ad62c5b89c698f1fd3c7685cb49705096cad702d02d93bcb1c27a409f4c9bddec001205ca4a2740f19b50900be81c7e847f1a863deea8d35701f1355cad8db57b1d4eb2ab4e29587734785abfb46ddede71928213d7d089dfdeda052827f459f1688cc0935bd47e7bcec27427c8376dcce7e22699567c0d145f8a7db33f6758815f1f15f9f7a9760dec4f34ae095edda4c64e9735bdd029c4e32c2ee31ba47ec5e6bdb97813d52dbd15b4e0b7a2c7f790ae64104d99f38c127f0a093288fa34144adb16b8968d4fa7656fcec99de8503dd46d3b03620a71c7cd085364abd30dccf7fbda25a1cdc102600149c9af1c97aa0372cd2e1909f28ac5c686f432b310e79528c9b8b9e8f314c1e74621ce6308ad2278b81d460892e0d9dd38b7c76d58be6dfd10ae7583ee1e7ef5b3f6f78dc60af0950df1b00cc55b6d178ba2e476bea0eaeef49323b83f05804159e7aef4eed4cc60dd07be76f067dfd0bcfb0b806b69ba921336a20c43c832d0cab8fa3ddeb29e3bf07b0d98a112eb07802756235a49d44a8b82a950d84e95e01971f0e106ccb337f07384e21620e0ad39e16ed9edca123226cf55ac44f449eeb53e38a7f27d101806e4823e4efcc887414240ee6826c4a5cb1c6443ad36ebf905a435c1d9054e54173911b17b5b40f60b3d9fd5f12eac54ca1e20191f5f18544d5fd3d665e9bcef96fb44b76110aa64d9db4c86c9513cbdad546538e8aec521fbe83ceac5e74a15629f1ed0b870a1d0d1e5680b6d6100d1bdd9a2ba3be3772f71854cb12ae9b3afd87cda738dcd107fe56a15f1877450cd0e", + "onion": "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71a060daf367132b378b3a3883c0e2c0e026b8900b2b5cdbc784e1a3bb913f88a9c50f7d61ab590531cf08000178a333a347f8b4072ed056f820f77774345e183a342ec4729f3d84accf515e88adddb85ecc08daba68404bae9a8e8d7178977d7094a1ae549f89338c0777551f874159eb42d3a59fb9285ad4e24883f27de23942ec966611e99bee1cee503455be9e8e642cef6cef7b9864130f692283f8a973d47a8f1c1726b6e59969385975c766e35737c8d76388b64f748ee7943ffb0e2ee45c57a1abc40762ae598723d21bd184e2b338f68ebff47219357bd19cd7e01e2337b806ef4d717888e129e59cd3dc31e6201ccb2fd6d7499836f37a993262468bcb3a4dcd03a22818aca49c6b7b9b8e9e870045631d8e039b066ff86e0d1b7291f71cefa7264c70404a8e538b566c17ccc5feab231401e6c08a01bd5edfc1aa8e3e533b96e82d1f91118d508924b923531929aea889fcdf050597c681185f336b1da63b0939aa2b7c50b21b5eb7b6ad66c81fab98a3cdf73f658149e7e9ced4edde5d38c9b8f92e16f6b4ab13d7fca6a0e4ecc9f9de611a90da6e99c39551094c56e3196f282c5dffd9fc4b2fc12f3bca8e6fe47eb45fbdd3be21a8a8d200797eae3c9a0497132f92410d804977408494dff49dd3d8bce248e0b74fd9e6f0f7102c25ddfa02bd9ad9f746abbfa337ef811d5345a9e16b60de1767b209645ba40bd1f9a5f75bc04feca9b27c5554be4fe83fac2cb83aa447a817bb85ae966c68b420063833fada375e2f515965e687a45699632902672c654d1d18d7bcbf55e8fa57f63f2da449f8e1e606e8722df081e5f193fc4179feb99ad22819afdeef211f7c54afdba92aeef0c00b7bc2b65a4813c01f907a8377585708f2d4c940a25328e585714c8ded0a9a4d7a6de1027c1cb7a0198cd3db68b58c0704dfd0cfbe624e9cd18cc0ae5d96697bb476708b9ee0403d211e64e0d5a7683a7a9a140c02f0ff1c6e67a302941b4052bdea8a63e70a3ad62c5b89c698f1fd3c7685cb49705096cad702d02d93bcb1c27a409f4c9bddec001205ca4a2740f19b50900be81c7e847f1a863deea8d35701f1355cad8db57b1d4eb2ab4e29587734785abfb46ddede71928213d7d089dfdeda052827f459f1688cc0935bd47e7bcec27427c8376dcce7e22699567c0d145f8a7db33f6758815f1f15f9f7a9760dec4f34ae095edda4c64e9735bdd029c4e32c2ee31ba47ec5e6bdb97813d52dbd15b4e0b7a2c7f790ae64104d99f38c127f0a093288fa34144adb16b8968d4fa7656fcec99de8503dd46d3b03620a71c7cd085364abd30dccf7fbda25a1cdc102600149c9af1c97aa0372cd2e1909f28ac5c686f432b310e79528c9b8b9e8f314c1e74621ce6308ad2278b81d460892e0d9dd38b7c76d58be6dfd10ae7583ee1e7ef5b3f6f78dc60af0950df1b00cc55b6d178ba2e476bea0eaeef49323b83f05804159e7aef4eed4cc60dd07be76f067dfd0bcfb0b806b69ba921336a20c43c832d0cab8fa3ddeb29e3bf07b0d98a112eb07802756235a49d44a8b82a950d84e95e01971f0e106ccb337f07384e21620e0ad39e16ed9edca123226cf55ac44f449eeb53e38a7f27d101806e4823e4efcc887414240ee6826c4a5cb1c6443ad36ebf905a435c1d9054e54173911b17b5b40f60b3d9fd5f12eac54ca1e20191f5f18544d5fd3d665e9bcef96fb44b76110aa64d9db4c86c9513cbdad546538e8aec521fbe83ceac5e74a15629f1ed0b870a1d0d1e5680b6d6100d1bd3f3b9043bd35b8919c4088f1949b8be89e4701eb870f8ed64fafa446c78df3ea", "decode": [ "4141414141414141414141414141414141414141414141414141414141414141", "4242424242424242424242424242424242424242424242424242424242424242", From a94cd91a344e3909c3f405876c3c70625cae1be1 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 7 Aug 2019 16:01:38 -0700 Subject: [PATCH 18/18] build: tidy go modules --- go.mod | 2 +- go.sum | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a6796d2..72d41f8 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,6 @@ require ( github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f 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 // indirect + 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 a596b77..3412e80 100644 --- a/go.sum +++ b/go.sum @@ -42,8 +42,10 @@ github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQz 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= @@ -59,6 +61,7 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 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= @@ -79,6 +82,7 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg 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= @@ -104,8 +108,10 @@ github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFF 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= @@ -131,8 +137,10 @@ github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWB 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= @@ -156,6 +164,7 @@ github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S0 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-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -202,16 +211,20 @@ 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=