bech32

Pure Haskell bech32, bech32m encoding/decoding (docs.ppad.tech/bech32).
git clone git://git.ppad.tech/bech32.git
Log | Files | Refs | README | LICENSE

Bech32m.hs (2919B)


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