2
2
{-# LANGUAGE CPP #-}
3
3
4
4
module Functora.Bolt11
5
- ( Hex (.. ),
5
+ ( Word5 ,
6
+ Hex (.. ),
6
7
inspectHex ,
7
8
Tag (.. ),
8
9
isPaymentHash ,
@@ -17,6 +18,10 @@ module Functora.Bolt11
17
18
)
18
19
where
19
20
21
+ import qualified Bitcoin.Address as Btc
22
+ import qualified Bitcoin.Address.Hash as Btc
23
+ import qualified Bitcoin.Address.SegWit as SegWit
24
+ import qualified Bitcoin.Address.Settings as Btc
20
25
import Codec.Binary.Bech32 (Word5 )
21
26
import qualified Codec.Binary.Bech32.Internal as Bech32
22
27
import Control.Applicative
@@ -75,7 +80,7 @@ data Tag
75
80
| DescriptionHash Hex
76
81
| Expiry Int
77
82
| MinFinalCltvExpiry Int
78
- | OnchainFallback -- TODO: address type
83
+ | OnchainFallback Btc. Address
79
84
| ExtraRouteInfo
80
85
| FeatureBits [Word5 ]
81
86
| UnknownTag Int [Word5 ]
@@ -93,6 +98,21 @@ data Network
93
98
| BitcoinSignet
94
99
deriving stock (Eq , Ord , Show , Data , Generic )
95
100
101
+ networkSettings :: Network -> Btc. Settings
102
+ networkSettings = \ case
103
+ BitcoinMainnet -> Btc. btc
104
+ BitcoinTestnet -> Btc. btcTestnet
105
+ BitcoinRegtest ->
106
+ Btc. btcTestnet
107
+ { Btc. settings_prefixSegWit =
108
+ fromMaybe (error " Bad prefixSegWit" ) $ Btc. prefixSegWit " bcrt"
109
+ }
110
+ BitcoinSignet ->
111
+ Btc. btcTestnet
112
+ { Btc. settings_prefixSegWit =
113
+ fromMaybe (error " Bad prefixSegWit" ) $ Btc. prefixSegWit " tbs"
114
+ }
115
+
96
116
data Multiplier = Milli | Micro | Nano | Pico
97
117
deriving stock (Eq , Ord , Show , Data , Generic )
98
118
@@ -220,13 +240,43 @@ w5int bytes = foldl' decodeInt 0 (zip [0 ..] (take 7 (reverse bytes)))
220
240
decodeInt ! n (i, byte) =
221
241
n .|. fromEnum byte `shiftL` (i * 5 )
222
242
223
- parseTag :: [Word5 ] -> (Maybe Tag , [Word5 ])
224
- parseTag [] = (Nothing , [] )
225
- parseTag ws@ [_] = (Nothing , ws)
226
- parseTag ws@ [_, _] = (Nothing , ws) -- appease the compiler warning gods
227
- parseTag ws
243
+ w5addr :: Network -> [Word5 ] -> Either String Btc. Address
244
+ w5addr _ [] = Left " Empty Onchain Fallback Address"
245
+ w5addr net (v0 : rest) =
246
+ case fromEnum v0 of
247
+ -- SegWit
248
+ vsn | vsn <= 16 -> do
249
+ sv0 <- first displayException $ tryFrom @ Int @ Word8 vsn
250
+ sv1 <-
251
+ maybe
252
+ (Left $ " Bad SegWit verion " <> inspect sv0)
253
+ Right
254
+ $ SegWit. version sv0
255
+ raw <- w5bs rest
256
+ res <- maybe (Left " Bad SegWit program" ) Right $ SegWit. program sv1 raw
257
+ pure $ Btc. SegWit (Btc. settings_prefixSegWit cfg) res
258
+ -- P2PKH
259
+ 17 -> do
260
+ raw <- w5bs rest
261
+ res <- maybe (Left " Bad PubHash160" ) Right $ Btc. parsePubHash160 raw
262
+ pure $ Btc. P2PKH (Btc. settings_prefixP2PKH cfg) res
263
+ -- P2SH
264
+ 18 -> do
265
+ raw <- w5bs rest
266
+ res <- maybe (Left " Bad ScriptHash160" ) Right $ Btc. parseScriptHash160 raw
267
+ pure $ Btc. P2SH (Btc. settings_prefixP2SH cfg) res
268
+ vsn -> do
269
+ Left $ " Bad Onchain Fallback verion " <> inspect vsn
270
+ where
271
+ cfg = networkSettings net
272
+
273
+ parseTag :: Network -> [Word5 ] -> (Maybe Tag , [Word5 ])
274
+ parseTag _ [] = (Nothing , [] )
275
+ parseTag _ ws@ [_] = (Nothing , ws)
276
+ parseTag _ ws@ [_, _] = (Nothing , ws) -- appease the compiler warning gods
277
+ parseTag _ ws
228
278
| length ws < 8 = (Nothing , ws)
229
- parseTag ws@ (t0 : d1 : d2 : rest)
279
+ parseTag net ws@ (t0 : d1 : d2 : rest)
230
280
| length rest < 7 = (Nothing , ws)
231
281
| otherwise = (Just tag, leftovers)
232
282
where
@@ -247,7 +297,7 @@ parseTag ws@(t0 : d1 : d2 : rest)
247
297
23 -> mkt DescriptionHash hex
248
298
6 -> Expiry $ w5int dat
249
299
24 -> MinFinalCltvExpiry $ w5int dat
250
- 9 -> OnchainFallback
300
+ 9 -> mkt OnchainFallback $ w5addr net dat
251
301
3 -> ExtraRouteInfo
252
302
5 -> FeatureBits dat
253
303
n -> UnknownTag n dat
@@ -257,22 +307,22 @@ data MSig
257
307
| Unk [Word5 ]
258
308
deriving stock (Eq , Ord , Show , Generic )
259
309
260
- parseTags :: [Word5 ] -> ([Tag ], MSig )
261
- parseTags ws
310
+ parseTags :: Network -> [Word5 ] -> ([Tag ], MSig )
311
+ parseTags net ws
262
312
| length ws == 104 = ([] , Sig ws)
263
313
| otherwise =
264
- let (mtag, rest) = parseTag ws
314
+ let (mtag, rest) = parseTag net ws
265
315
in maybe
266
316
([] , Unk rest)
267
- (\ tag -> first (tag : ) (parseTags rest))
317
+ (\ tag -> first (tag : ) (parseTags net rest))
268
318
mtag
269
319
270
320
decodeBolt11 :: Text -> Either String Bolt11
271
321
decodeBolt11 raw = do
272
322
(rawHrp, rawDp) <- first show $ Bech32. decodeLenient raw
273
323
hrp <- Atto. parseOnly parseHrp $ Bech32. humanReadablePartToText rawHrp
274
324
let (ts, rest) = splitAt 7 $ Bech32. dataPartToWords rawDp
275
- let (tags, leftover) = parseTags rest
325
+ let (tags, leftover) = parseTags (bolt11HrpNet hrp) rest
276
326
(rawSig, recFlag) <- case leftover of
277
327
Sig ws -> Right $ splitAt 103 ws
278
328
Unk left -> Left (" corrupt, leftover: " <> show left)
0 commit comments