Skip to content

Commit e3caf6d

Browse files
committed
bolt11 extra routing info parser
1 parent 4d1145f commit e3caf6d

File tree

5 files changed

+95
-3
lines changed

5 files changed

+95
-3
lines changed

ghcjs/lightning-verifier/lightning-verifier.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ common pkg
9393
TypeOperators
9494

9595
build-depends:
96+
, aeson
9697
, async
9798
, barbies
9899
, base

ghcjs/lightning-verifier/src/App/Widgets/Bolt11.hs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ where
55

66
import App.Types
77
import qualified Bitcoin.Address as Btc
8+
import qualified Data.Aeson as A
89
import qualified Data.ByteString.Base16 as B16
10+
import qualified Data.ByteString.Lazy as BL
911
import qualified Data.Text.Encoding as T
1012
import qualified Functora.Bolt11 as B11
1113
import Functora.Miso.Prelude
@@ -104,7 +106,13 @@ invoiceTagWidget ln = \case
104106
--
105107
txt <- either (const mempty) pure . decodeUtf8' $ Btc.renderAddress x
106108
pure $ pair "Onchain Fallback" $ from @Prelude.Text @MisoString txt
107-
B11.ExtraRouteInfo -> mempty
109+
B11.ExtraRouteInfo x ->
110+
pure
111+
. pair "Extra Routing Info"
112+
. either (const mempty) (from @Prelude.Text @MisoString)
113+
. decodeUtf8'
114+
. from @BL.ByteString @ByteString
115+
$ A.encode x
108116
B11.Features x -> pure . pair "Feature Bits" $ B11.inspectFeatures x
109117
B11.UnknownTag {} -> mempty
110118
B11.UnparsedTag {} -> mempty

pub/functora/functora.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,12 @@ common pkg-bolt11
246246
import: pkg
247247
hs-source-dirs: src/bolt11
248248
build-depends:
249+
, aeson
249250
, attoparsec
250251
, base
251252
, base16-bytestring
252253
, bech32
254+
, binary
253255
, bitcoin-address
254256
, bytestring
255257
, text

pub/functora/src/bolt11/Functora/Bolt11.hs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module Functora.Bolt11
1111
inspectFeature,
1212
inspectFeatures,
1313
RequiredOrSupported (..),
14+
Route (..),
1415
isPaymentHash,
1516
Network (..),
1617
Multiplier (..),
@@ -19,6 +20,7 @@ module Functora.Bolt11
1920
Bolt11Hrp (..),
2021
Bolt11Sig (..),
2122
Bolt11 (..),
23+
inspectW5,
2224
decodeBolt11,
2325
)
2426
where
@@ -30,7 +32,9 @@ import qualified Bitcoin.Address.Settings as Btc
3032
import Codec.Binary.Bech32 (Word5)
3133
import qualified Codec.Binary.Bech32.Internal as Bech32
3234
import Control.Applicative
35+
import qualified Data.Aeson as A
3336
import qualified Data.Attoparsec.Text as Atto
37+
import qualified Data.Binary.Get as BG
3438
import Data.Bits (shiftL, (.&.), (.|.))
3539
import qualified Data.ByteString as BS
3640
import qualified Data.ByteString.Base16 as B16
@@ -86,7 +90,7 @@ data Tag
8690
| Expiry Int
8791
| MinFinalCltvExpiry Int
8892
| OnchainFallback Btc.Address
89-
| ExtraRouteInfo
93+
| ExtraRouteInfo [Route]
9094
| Features [Feature]
9195
| UnknownTag Int [Word5]
9296
| UnparsedTag Int [Word5] String
@@ -195,6 +199,59 @@ data RequiredOrSupported
195199
| Supported
196200
deriving stock (Eq, Ord, Show, Data, Generic)
197201

202+
data Route = Route
203+
{ routePubKey :: Hex,
204+
routeShortChanId :: Hex,
205+
routeFeeBaseMsat :: Word32,
206+
routeFeePropMillionth :: Word32,
207+
routeCltvExpiryDelta :: Word16
208+
}
209+
deriving stock (Eq, Ord, Show, Data, Generic)
210+
211+
instance A.ToJSON Route where
212+
toJSON x =
213+
A.object
214+
[ "pubkey" A..= inspectHex @Text (routePubKey x),
215+
"short_channel_id" A..= inspectHex @Text (routeShortChanId x),
216+
"fee_base_msat" A..= routeFeeBaseMsat x,
217+
"fee_proportional_millionths" A..= routeFeePropMillionth x,
218+
"cltv_expiry_delta" A..= routeCltvExpiryDelta x
219+
]
220+
toEncoding x =
221+
A.pairs
222+
$ "pubkey"
223+
A..= inspectHex @Text (routePubKey x)
224+
<> "short_channel_id"
225+
A..= inspectHex @Text (routeShortChanId x)
226+
<> "fee_base_msat"
227+
A..= routeFeeBaseMsat x
228+
<> "fee_proportional_millionths"
229+
A..= routeFeePropMillionth x
230+
<> "cltv_expiry_delta"
231+
A..= routeCltvExpiryDelta x
232+
233+
parseRoutes :: ByteString -> Either String [Route]
234+
parseRoutes =
235+
bimap thd3 thd3
236+
. BG.runGetOrFail
237+
( many
238+
$ do
239+
pub <- BG.getByteString 33
240+
chan <- BG.getByteString 8
241+
base <- BG.getWord32be
242+
prop <- BG.getWord32be
243+
cltv <- BG.getWord16be
244+
pure
245+
Route
246+
{ routePubKey = Hex pub,
247+
routeShortChanId = Hex chan,
248+
routeFeeBaseMsat = base,
249+
routeFeePropMillionth = prop,
250+
routeCltvExpiryDelta = cltv
251+
}
252+
)
253+
. BL.fromStrict
254+
198255
isPaymentHash :: Tag -> Bool
199256
isPaymentHash PaymentHash {} = True
200257
isPaymentHash _ = False
@@ -382,6 +439,13 @@ w5addr net (v0 : rest) =
382439
where
383440
cfg = networkSettings net
384441

442+
inspectW5 :: forall a. (From Text a) => [Word5] -> a
443+
inspectW5 ws =
444+
from @Text @a
445+
. either (const . inspect $ fmap fromEnum ws) id
446+
$ w5txt ws
447+
<|> fmap inspect (w5bs ws)
448+
385449
parseTag :: Network -> [Word5] -> (Maybe Tag, [Word5])
386450
parseTag _ [] = (Nothing, [])
387451
parseTag _ ws@[_] = (Nothing, ws)
@@ -410,7 +474,7 @@ parseTag net ws@(t0 : d1 : d2 : rest)
410474
6 -> Expiry $ w5int dat
411475
24 -> MinFinalCltvExpiry $ w5int dat
412476
9 -> mkt OnchainFallback $ w5addr net dat
413-
3 -> ExtraRouteInfo
477+
3 -> mkt ExtraRouteInfo $ parseRoutes =<< w5bs dat
414478
5 -> Features $ parseFeatures dat
415479
n -> UnknownTag n dat
416480

pub/functora/src/test/Functora/Bolt11Spec.hs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,23 @@ goodSamples =
5757
. Btc.parsePubHash160
5858
$ unHex "04b61f7dc1ea0dc99424464cc4064dc564d91e89",
5959
ExtraRouteInfo
60+
[ Route
61+
{ routePubKey =
62+
"029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255",
63+
routeShortChanId = "0102030405060708",
64+
routeFeeBaseMsat = 1,
65+
routeFeePropMillionth = 20,
66+
routeCltvExpiryDelta = 3
67+
},
68+
Route
69+
{ routePubKey =
70+
"039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255",
71+
routeShortChanId = "030405060708090a",
72+
routeFeeBaseMsat = 2,
73+
routeFeePropMillionth = 30,
74+
routeCltvExpiryDelta = 4
75+
}
76+
]
6077
],
6178
bolt11Signature =
6279
Bolt11Sig

0 commit comments

Comments
 (0)