Skip to content

Commit a4dc2f2

Browse files
committed
bolt11 feature flags parser
1 parent 1156fc2 commit a4dc2f2

File tree

3 files changed

+104
-7
lines changed

3 files changed

+104
-7
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ invoiceTagWidget = \case
103103
txt <- either (const mempty) pure . decodeUtf8' $ Btc.renderAddress x
104104
pure $ pair "Onchain Fallback" $ from @Prelude.Text @MisoString txt
105105
B11.ExtraRouteInfo -> mempty
106-
B11.FeatureBits x -> w5s "Feature Bits" x
106+
B11.Features x -> pure . pair "Feature Bits" $ inspect x
107107
B11.UnknownTag {} -> mempty
108108
B11.UnparsedTag {} -> mempty
109109
where

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

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ module Functora.Bolt11
66
Hex (..),
77
inspectHex,
88
Tag (..),
9+
Feature (..),
10+
FeatureName (..),
11+
RequiredOrSupported (..),
912
isPaymentHash,
1013
Network (..),
1114
Multiplier (..),
@@ -26,7 +29,7 @@ import Codec.Binary.Bech32 (Word5)
2629
import qualified Codec.Binary.Bech32.Internal as Bech32
2730
import Control.Applicative
2831
import qualified Data.Attoparsec.Text as Atto
29-
import Data.Bits (shiftL, (.|.))
32+
import Data.Bits (shiftL, (.&.), (.|.))
3033
import qualified Data.ByteString as BS
3134
import qualified Data.ByteString.Base16 as B16
3235
import qualified Data.ByteString.Builder as BS
@@ -82,11 +85,98 @@ data Tag
8285
| MinFinalCltvExpiry Int
8386
| OnchainFallback Btc.Address
8487
| ExtraRouteInfo
85-
| FeatureBits [Word5]
88+
| Features [Feature]
8689
| UnknownTag Int [Word5]
8790
| UnparsedTag Int [Word5] String
8891
deriving stock (Eq, Ord, Show, Generic)
8992

93+
data Feature = Feature
94+
{ featureBits :: Int,
95+
featureName :: FeatureName,
96+
featureRequiredOrSuported :: RequiredOrSupported
97+
}
98+
deriving stock (Eq, Ord, Show, Data, Generic)
99+
100+
data FeatureName
101+
= Option_data_loss_protect
102+
| Option_upfront_shutdown_script
103+
| Gossip_queries
104+
| Var_onion_optin
105+
| Gossip_queries_ex
106+
| Option_static_remotekey
107+
| Payment_secret
108+
| Basic_mpp
109+
| Option_support_large_channel
110+
| Option_anchors
111+
| Option_route_blinding
112+
| Option_shutdown_anysegwit
113+
| Option_dual_fund
114+
| Option_quiesce
115+
| Option_onion_messages
116+
| Option_channel_type
117+
| Option_scid_alias
118+
| Option_payment_metadata
119+
| Option_zeroconf
120+
| Unknown_feature
121+
deriving stock (Eq, Ord, Show, Data, Generic)
122+
123+
parseFeatures :: [Word5] -> [Feature]
124+
parseFeatures [] = mempty
125+
parseFeatures ws = sort . nubOrd $ do
126+
(w, idx) <- zip ws [0 ..]
127+
i <- [0 .. bsize - 1]
128+
if fromEnum w .&. shiftL 1 i == 0
129+
then mempty
130+
else do
131+
let end = length ws - 1
132+
let bit = (end - idx) * bsize + i
133+
pure $ parseFeature bit
134+
where
135+
bsize :: Int
136+
bsize = 5
137+
138+
parseFeature :: Int -> Feature
139+
parseFeature bit =
140+
Feature
141+
{ featureBits = bit,
142+
featureName = name,
143+
featureRequiredOrSuported =
144+
if even bit
145+
then Required
146+
else Supported
147+
}
148+
where
149+
bits :: [Int] -> Bool
150+
bits = elem bit
151+
name :: FeatureName
152+
name =
153+
if
154+
| bits [0, 1] -> Option_data_loss_protect
155+
| bits [4, 5] -> Option_upfront_shutdown_script
156+
| bits [6, 7] -> Gossip_queries
157+
| bits [8, 9] -> Var_onion_optin
158+
| bits [10, 11] -> Gossip_queries_ex
159+
| bits [12, 13] -> Option_static_remotekey
160+
| bits [14, 15] -> Payment_secret
161+
| bits [16, 17] -> Basic_mpp
162+
| bits [18, 19] -> Option_support_large_channel
163+
| bits [22, 23] -> Option_anchors
164+
| bits [24, 25] -> Option_route_blinding
165+
| bits [26, 27] -> Option_shutdown_anysegwit
166+
| bits [28, 29] -> Option_dual_fund
167+
| bits [34, 35] -> Option_quiesce
168+
| bits [38, 39] -> Option_onion_messages
169+
| bits [44, 45] -> Option_channel_type
170+
| bits [46, 47] -> Option_scid_alias
171+
| bits [48, 49] -> Option_payment_metadata
172+
| bits [50, 51] -> Option_zeroconf
173+
| otherwise -> Unknown_feature
174+
175+
data RequiredOrSupported
176+
= Required
177+
| Supported
178+
deriving stock (Eq, Ord, Show, Data, Generic)
179+
90180
isPaymentHash :: Tag -> Bool
91181
isPaymentHash PaymentHash {} = True
92182
isPaymentHash _ = False
@@ -221,10 +311,14 @@ parseHrp = do
221311
amt <- optional parseHrpAmount
222312
pure (Bolt11Hrp net amt)
223313

314+
w5w8 :: [Word5] -> Either String [Word8]
315+
w5w8 =
316+
maybe (Left "Non-Base256 bits") Right
317+
. Bech32.toBase256
318+
224319
w5bs :: [Word5] -> Either String ByteString
225320
w5bs =
226-
maybe (Left "Non-Base256 bits") (Right . BS.pack)
227-
. Bech32.toBase256
321+
fmap BS.pack . w5w8
228322

229323
w5hex :: [Word5] -> Either String Hex
230324
w5hex =
@@ -299,7 +393,7 @@ parseTag net ws@(t0 : d1 : d2 : rest)
299393
24 -> MinFinalCltvExpiry $ w5int dat
300394
9 -> mkt OnchainFallback $ w5addr net dat
301395
3 -> ExtraRouteInfo
302-
5 -> FeatureBits dat
396+
5 -> Features $ parseFeatures dat
303397
n -> UnknownTag n dat
304398

305399
data MSig

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ goodSamples =
2121
[ PaymentSecret "1111111111111111111111111111111111111111111111111111111111111111",
2222
PaymentHash "0001020304050607080900010203040506070809000102030405060708090102",
2323
Description "Please consider supporting this project",
24-
FeatureBits $ fmap toEnum [16, 8, 0]
24+
Features
25+
[ Feature 8 Var_onion_optin Required,
26+
Feature 14 Payment_secret Required
27+
]
2528
],
2629
bolt11Signature =
2730
Bolt11Sig

0 commit comments

Comments
 (0)