Bech32.hs (3002B)
1 {-# OPTIONS_HADDOCK prune #-} 2 {-# LANGUAGE ViewPatterns #-} 3 4 -- | 5 -- Module: Data.ByteString.Bech32 6 -- Copyright: (c) 2024 Jared Tobin 7 -- License: MIT 8 -- Maintainer: Jared Tobin <jared@ppad.tech> 9 -- 10 -- The 11 -- [BIP0173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) 12 -- bech32 checksummed base32 encoding, with decoding and checksum verification. 13 14 module Data.ByteString.Bech32 ( 15 -- * Encoding and Decoding 16 encode 17 , decode 18 19 -- * Checksum 20 , verify 21 ) where 22 23 import Control.Monad (guard) 24 import qualified Data.ByteString as BS 25 import qualified Data.ByteString.Char8 as B8 26 import qualified Data.ByteString.Base32 as B32 27 import qualified Data.ByteString.Bech32.Internal as BI 28 import qualified Data.ByteString.Builder as BSB 29 import qualified Data.ByteString.Builder.Extra as BE 30 import qualified Data.ByteString.Internal as BSI 31 import qualified Data.Char as C (toLower, isLower, isAlpha) 32 33 -- realization for small builders 34 toStrict :: BSB.Builder -> BS.ByteString 35 toStrict = BS.toStrict 36 . BE.toLazyByteStringWith (BE.safeStrategy 128 BE.smallChunkSize) mempty 37 {-# INLINE toStrict #-} 38 39 create_checksum :: BS.ByteString -> BS.ByteString -> BS.ByteString 40 create_checksum = BI.create_checksum BI.Bech32 41 42 -- | Encode a base256 human-readable part and input as bech32. 43 -- 44 -- >>> let Just bech32 = encode "bc" "my string" 45 -- >>> bech32 46 -- "bc1d4ujqum5wf5kuecmu02w2" 47 encode 48 :: BS.ByteString -- ^ base256-encoded human-readable part 49 -> BS.ByteString -- ^ base256-encoded data part 50 -> Maybe BS.ByteString -- ^ bech32-encoded bytestring 51 encode (B8.map C.toLower -> hrp) (B32.encode -> dat) = do 52 guard (BI.valid_hrp hrp) 53 ws <- BI.as_word5 dat 54 let check = create_checksum hrp ws 55 res = toStrict $ 56 BSB.byteString hrp 57 <> BSB.word8 49 -- 1 58 <> BSB.byteString dat 59 <> BSB.byteString (BI.as_base32 check) 60 guard (BS.length res < 91) 61 pure res 62 63 -- | Decode a bech32-encoded 'ByteString' into its human-readable and data 64 -- parts. 65 -- 66 -- >>> decode "hi1df6x7cnfdcs8wctnyp5x2un9wed5st" 67 -- Just ("hi","jtobin was here") 68 -- >>> decode "hey1df6x7cnfdcs8wctnyp5x2un9wed5st" -- s/hi/hey 69 -- Nothing 70 decode 71 :: BS.ByteString -- ^ bech23-encoded bytestring 72 -> Maybe (BS.ByteString, BS.ByteString) -- ^ (hrp, data less checksum) 73 decode bs@(BSI.PS _ _ l) = do 74 guard (l <= 90) 75 guard (B8.all (\a -> if C.isAlpha a then C.isLower a else True) bs) 76 guard (verify bs) 77 sep <- BS.elemIndexEnd 0x31 bs 78 case BS.splitAt sep bs of 79 (hrp, raw) -> do 80 guard (BI.valid_hrp hrp) 81 guard (BS.length raw >= 6) 82 (_, BS.dropEnd 6 -> bech32dat) <- BS.uncons raw 83 dat <- B32.decode bech32dat 84 pure (hrp, dat) 85 86 -- | Verify that a bech32 string has a valid checksum. 87 -- 88 -- >>> verify "bc1d4ujqum5wf5kuecmu02w2" 89 -- True 90 -- >>> verify "bc1d4ujquw5wf5kuecmu02w2" -- s/m/w 91 -- False 92 verify 93 :: BS.ByteString -- ^ bech32-encoded bytestring 94 -> Bool 95 verify = BI.verify BI.Bech32 96