bech32

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

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