20
20
import Codec.Binary.Bech32 (Word5 )
21
21
import qualified Codec.Binary.Bech32.Internal as Bech32
22
22
import Control.Applicative
23
- import Data.Attoparsec.Text
23
+ import qualified Data.Attoparsec.Text as Atto
24
24
import Data.Bits (shiftL , (.|.) )
25
25
import qualified Data.ByteString as BS
26
26
import qualified Data.ByteString.Base16 as B16
@@ -29,7 +29,7 @@ import qualified Data.ByteString.Lazy.Char8 as BL
29
29
import qualified Data.Text as T
30
30
import qualified Data.Text.Encoding as T
31
31
import Functora.Prelude hiding (error )
32
- import Prelude (Show (.. ), error , splitAt , take )
32
+ import Prelude (Show (.. ), error )
33
33
34
34
newtype Hex = Hex
35
35
{ unHex :: ByteString
@@ -70,13 +70,16 @@ data Tag
70
70
= PaymentHash Hex
71
71
| PaymentSecret Hex
72
72
| Description Text
73
+ | AdditionalMetadata [Word5 ]
73
74
| PayeePubkey Hex
74
75
| DescriptionHash Hex
75
76
| Expiry Int
76
77
| MinFinalCltvExpiry Int
77
78
| OnchainFallback -- TODO: address type
78
79
| ExtraRouteInfo
79
80
| FeatureBits [Word5 ]
81
+ | UnknownTag Int [Word5 ]
82
+ | UnparsedTag Int [Word5 ] String
80
83
deriving stock (Eq , Ord , Show , Generic )
81
84
82
85
isPaymentHash :: Tag -> Bool
@@ -167,75 +170,87 @@ data Bolt11 = Bolt11
167
170
}
168
171
deriving stock (Eq , Ord , Show , Generic )
169
172
170
- parseNetwork :: Parser Network
173
+ parseNetwork :: Atto. Parser Network
171
174
parseNetwork =
172
- (string " bcrt" *> pure BitcoinRegtest )
173
- <|> (string " bc" *> pure BitcoinMainnet )
174
- <|> (string " tbs" *> pure BitcoinSignet )
175
- <|> (string " tb" *> pure BitcoinTestnet )
175
+ (Atto. string " bcrt" *> pure BitcoinRegtest )
176
+ <|> (Atto. string " bc" *> pure BitcoinMainnet )
177
+ <|> (Atto. string " tbs" *> pure BitcoinSignet )
178
+ <|> (Atto. string " tb" *> pure BitcoinTestnet )
176
179
177
- parseMultiplier :: Parser Multiplier
180
+ parseMultiplier :: Atto. Parser Multiplier
178
181
parseMultiplier = do
179
- c <- satisfy (`elem` (" munp" :: String ))
182
+ c <- Atto. satisfy (`elem` (" munp" :: String ))
180
183
case c of
181
184
' m' -> pure Milli
182
185
' u' -> pure Micro
183
186
' n' -> pure Nano
184
187
' p' -> pure Pico
185
188
_ -> fail " unhandled case in parseMultiplier"
186
189
187
- parseHrpAmount :: Parser Bolt11HrpAmt
190
+ parseHrpAmount :: Atto. Parser Bolt11HrpAmt
188
191
parseHrpAmount = do
189
- amt <- decimal
192
+ amt <- Atto. decimal
190
193
mul <- parseMultiplier
191
194
pure $ Bolt11HrpAmt amt mul
192
195
193
- parseHrp :: Parser Bolt11Hrp
196
+ parseHrp :: Atto. Parser Bolt11Hrp
194
197
parseHrp = do
195
- _ <- char ' l'
196
- _ <- char ' n'
198
+ _ <- Atto. char ' l'
199
+ _ <- Atto. char ' n'
197
200
net <- parseNetwork
198
201
amt <- optional parseHrpAmount
199
202
pure (Bolt11Hrp net amt)
200
203
204
+ w5bs :: [Word5 ] -> Either String ByteString
205
+ w5bs =
206
+ maybe (Left " Non-Base256 bits" ) (Right . BS. pack)
207
+ . Bech32. toBase256
208
+
209
+ w5hex :: [Word5 ] -> Either String Hex
210
+ w5hex =
211
+ second Hex . w5bs
212
+
213
+ w5txt :: [Word5 ] -> Either String Text
214
+ w5txt =
215
+ first displayException . decodeUtf8' <=< w5bs
216
+
201
217
w5int :: [Word5 ] -> Int
202
- w5int bytes = foldl' decodeInt 0 (zip [0 .. ] (Prelude. take 7 (reverse bytes)))
218
+ w5int bytes = foldl' decodeInt 0 (zip [0 .. ] (take 7 (reverse bytes)))
203
219
where
204
220
decodeInt ! n (i, byte) =
205
221
n .|. fromEnum byte `shiftL` (i * 5 )
206
222
207
- w5bs :: [Word5 ] -> ByteString
208
- w5bs = BS. pack . fromMaybe (error " what" ) . Bech32. toBase256
209
-
210
- w5txt :: [Word5 ] -> Text
211
- w5txt = decodeUtf8 . w5bs
212
-
213
223
parseTag :: [Word5 ] -> (Maybe Tag , [Word5 ])
214
224
parseTag [] = (Nothing , [] )
215
225
parseTag ws@ [_] = (Nothing , ws)
216
226
parseTag ws@ [_, _] = (Nothing , ws) -- appease the compiler warning gods
217
227
parseTag ws
218
228
| length ws < 8 = (Nothing , ws)
219
- parseTag ws@ (typ : d1 : d2 : rest)
229
+ parseTag ws@ (t0 : d1 : d2 : rest)
220
230
| length rest < 7 = (Nothing , ws)
221
231
| otherwise = (Just tag, leftovers)
222
232
where
223
- dataLen = w5int [d1, d2]
224
- (dat, leftovers) = Prelude. splitAt dataLen rest
225
- datBs = Hex (w5bs dat)
233
+ typ = fromEnum t0
234
+ len = w5int [d1, d2]
235
+ (dat, leftovers) = splitAt len rest
236
+ hex = w5hex dat
237
+ txt = w5txt dat
238
+ mkt :: (a -> Tag ) -> Either String a -> Tag
239
+ mkt con = either (UnparsedTag typ dat) con
226
240
tag =
227
- case fromEnum typ of
228
- 1 -> PaymentHash datBs
229
- 16 -> PaymentSecret datBs
230
- 23 -> DescriptionHash datBs -- (w5bs dat)
231
- 13 -> Description (w5txt dat)
232
- 19 -> PayeePubkey datBs
233
- 6 -> Expiry (w5int dat)
234
- 24 -> MinFinalCltvExpiry (w5int dat)
241
+ case typ of
242
+ 1 -> mkt PaymentHash hex
243
+ 16 -> mkt PaymentSecret hex
244
+ 13 -> mkt Description txt
245
+ 27 -> AdditionalMetadata dat
246
+ 19 -> mkt PayeePubkey hex
247
+ 23 -> mkt DescriptionHash hex
248
+ 6 -> Expiry $ w5int dat
249
+ 24 -> MinFinalCltvExpiry $ w5int dat
235
250
9 -> OnchainFallback
236
251
3 -> ExtraRouteInfo
237
252
5 -> FeatureBits dat
238
- n -> error ( " unhandled typ " ++ show n)
253
+ n -> UnknownTag n dat
239
254
240
255
data MSig
241
256
= Sig [Word5 ]
@@ -255,12 +270,12 @@ parseTags ws
255
270
decodeBolt11 :: Text -> Either String Bolt11
256
271
decodeBolt11 raw = do
257
272
(rawHrp, rawDp) <- first show $ Bech32. decodeLenient raw
258
- hrp <- parseOnly parseHrp $ Bech32. humanReadablePartToText rawHrp
273
+ hrp <- Atto. parseOnly parseHrp $ Bech32. humanReadablePartToText rawHrp
259
274
let (ts, rest) = splitAt 7 $ Bech32. dataPartToWords rawDp
260
275
let (tags, leftover) = parseTags rest
261
276
(rawSig, recFlag) <- case leftover of
262
277
Sig ws -> Right $ splitAt 103 ws
263
- Unk left -> Left (" corrupt, leftover: " ++ show ( Hex (w5bs left)) )
278
+ Unk left -> Left (" corrupt, leftover: " <> show left)
264
279
sig <-
265
280
maybe (Left " corrupt" ) Right $ Bech32. toBase256 rawSig
266
281
Right
0 commit comments