bech32

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

commit ba6d5429cdbcfe1b5da2933ddd3b390313013b79
parent 2e120b00efd9c2b3d98632f4ec0e012758048e35
Author: Jared Tobin <jared@jtobin.io>
Date:   Fri,  3 Jan 2025 15:35:03 -0330

lib: refactor encode

Diffstat:
Mlib/Data/ByteString/Base32.hs | 179++++++++++++++++++++++++++++++++++++++-----------------------------------------
1 file changed, 86 insertions(+), 93 deletions(-)

diff --git a/lib/Data/ByteString/Base32.hs b/lib/Data/ByteString/Base32.hs @@ -28,6 +28,7 @@ import qualified Data.Bits as B import qualified Data.ByteString as BS import qualified Data.ByteString.Builder as BSB import qualified Data.ByteString.Builder.Extra as BE +import qualified Data.ByteString.Internal as BI import qualified Data.ByteString.Unsafe as BU import Data.Word (Word8, Word32, Word64) @@ -93,95 +94,88 @@ encode encode dat = toStrict (go dat) where bech32_char = fi . BS.index bech32_charset . fi - go bs = case BS.splitAt 5 bs of - (chunk, etc) -> case BS.length etc of - -- https://datatracker.ietf.org/doc/html/rfc4648#section-6 - 0 | BS.length chunk == 5 -> case BS.unsnoc chunk of - Nothing -> error "impossible, chunk length is 5" - Just (word32be -> w32, fi -> w8) -> arrange w32 w8 - - | BS.length chunk == 1 -> - let a = BU.unsafeIndex chunk 0 - t = bech32_char ((a .&. 0b11111000) `B.shiftR` 3) - u = bech32_char ((a .&. 0b00000111) `B.shiftL` 2) - - !w16 = fi t - .|. fi u `B.shiftL` 8 - - in BSB.word16LE w16 - - | BS.length chunk == 2 -> - let a = BU.unsafeIndex chunk 0 - b = BU.unsafeIndex chunk 1 - t = bech32_char ((a .&. 0b11111000) `B.shiftR` 3) - u = bech32_char $ - ((a .&. 0b00000111) `B.shiftL` 2) - .|. ((b .&. 0b11000000) `B.shiftR` 6) - v = bech32_char ((b .&. 0b00111110) `B.shiftR` 1) - w = bech32_char ((b .&. 0b00000001) `B.shiftL` 4) - - !w32 = fi t - .|. fi u `B.shiftL` 8 - .|. fi v `B.shiftL` 16 - .|. fi w `B.shiftL` 24 - - in BSB.word32LE w32 - - | BS.length chunk == 3 -> - let a = BU.unsafeIndex chunk 0 - b = BU.unsafeIndex chunk 1 - c = BU.unsafeIndex chunk 2 - t = bech32_char ((a .&. 0b11111000) `B.shiftR` 3) - u = bech32_char $ - ((a .&. 0b00000111) `B.shiftL` 2) - .|. ((b .&. 0b11000000) `B.shiftR` 6) - v = bech32_char ((b .&. 0b00111110) `B.shiftR` 1) - w = bech32_char $ - ((b .&. 0b00000001) `B.shiftL` 4) - .|. ((c .&. 0b11110000) `B.shiftR` 4) - x = bech32_char ((c .&. 0b00001111) `B.shiftL` 1) - - !w32 = fi t - .|. fi u `B.shiftL` 8 - .|. fi v `B.shiftL` 16 - .|. fi w `B.shiftL` 24 - - in BSB.word32LE w32 <> BSB.word8 x - - | BS.length chunk == 4 -> - let a = BU.unsafeIndex chunk 0 - b = BU.unsafeIndex chunk 1 - c = BU.unsafeIndex chunk 2 - d = BU.unsafeIndex chunk 3 - t = bech32_char ((a .&. 0b11111000) `B.shiftR` 3) - u = bech32_char $ - ((a .&. 0b00000111) `B.shiftL` 2) - .|. ((b .&. 0b11000000) `B.shiftR` 6) - v = bech32_char ((b .&. 0b00111110) `B.shiftR` 1) - w = bech32_char $ - ((b .&. 0b00000001) `B.shiftL` 4) - .|. ((c .&. 0b11110000) `B.shiftR` 4) - x = bech32_char $ - ((c .&. 0b00001111) `B.shiftL` 1) - .|. ((d .&. 0b10000000) `B.shiftR` 7) - y = bech32_char ((d .&. 0b01111100) `B.shiftR` 2) - z = bech32_char ((d .&. 0b00000011) `B.shiftL` 3) - - !w32 = fi t - .|. fi u `B.shiftL` 8 - .|. fi v `B.shiftL` 16 - .|. fi w `B.shiftL` 24 - - !w16 = fi x - .|. fi y `B.shiftL` 8 - - in BSB.word32LE w32 <> BSB.word16LE w16 <> BSB.word8 z - - | otherwise -> mempty - - _ -> case BS.unsnoc chunk of - Nothing -> error "impossible, chunk length is 5" - Just (word32be -> w32, w8) -> arrange w32 w8 <> go etc + go bs@(BI.PS _ _ l) + | l >= 5 = case BS.splitAt 5 bs of + (chunk, etc) -> case BS.unsnoc chunk of + Nothing -> error "impossible, chunk length is 5" + Just (word32be -> w32, w8) -> arrange w32 w8 <> go etc + | l == 0 = mempty + | l == 1 = + let a = BU.unsafeIndex bs 0 + t = bech32_char ((a .&. 0b11111000) `B.shiftR` 3) + u = bech32_char ((a .&. 0b00000111) `B.shiftL` 2) + + !w16 = fi t + .|. fi u `B.shiftL` 8 + + in BSB.word16LE w16 + | l == 2 = + let a = BU.unsafeIndex bs 0 + b = BU.unsafeIndex bs 1 + t = bech32_char ((a .&. 0b11111000) `B.shiftR` 3) + u = bech32_char $ + ((a .&. 0b00000111) `B.shiftL` 2) + .|. ((b .&. 0b11000000) `B.shiftR` 6) + v = bech32_char ((b .&. 0b00111110) `B.shiftR` 1) + w = bech32_char ((b .&. 0b00000001) `B.shiftL` 4) + + !w32 = fi t + .|. fi u `B.shiftL` 8 + .|. fi v `B.shiftL` 16 + .|. fi w `B.shiftL` 24 + + in BSB.word32LE w32 + | l == 3 = + let a = BU.unsafeIndex bs 0 + b = BU.unsafeIndex bs 1 + c = BU.unsafeIndex bs 2 + t = bech32_char ((a .&. 0b11111000) `B.shiftR` 3) + u = bech32_char $ + ((a .&. 0b00000111) `B.shiftL` 2) + .|. ((b .&. 0b11000000) `B.shiftR` 6) + v = bech32_char ((b .&. 0b00111110) `B.shiftR` 1) + w = bech32_char $ + ((b .&. 0b00000001) `B.shiftL` 4) + .|. ((c .&. 0b11110000) `B.shiftR` 4) + x = bech32_char ((c .&. 0b00001111) `B.shiftL` 1) + + !w32 = fi t + .|. fi u `B.shiftL` 8 + .|. fi v `B.shiftL` 16 + .|. fi w `B.shiftL` 24 + + in BSB.word32LE w32 <> BSB.word8 x + | l == 4 = + let a = BU.unsafeIndex bs 0 + b = BU.unsafeIndex bs 1 + c = BU.unsafeIndex bs 2 + d = BU.unsafeIndex bs 3 + t = bech32_char ((a .&. 0b11111000) `B.shiftR` 3) + u = bech32_char $ + ((a .&. 0b00000111) `B.shiftL` 2) + .|. ((b .&. 0b11000000) `B.shiftR` 6) + v = bech32_char ((b .&. 0b00111110) `B.shiftR` 1) + w = bech32_char $ + ((b .&. 0b00000001) `B.shiftL` 4) + .|. ((c .&. 0b11110000) `B.shiftR` 4) + x = bech32_char $ + ((c .&. 0b00001111) `B.shiftL` 1) + .|. ((d .&. 0b10000000) `B.shiftR` 7) + y = bech32_char ((d .&. 0b01111100) `B.shiftR` 2) + z = bech32_char ((d .&. 0b00000011) `B.shiftL` 3) + + !w32 = fi t + .|. fi u `B.shiftL` 8 + .|. fi v `B.shiftL` 16 + .|. fi w `B.shiftL` 24 + + !w16 = fi x + .|. fi y `B.shiftL` 8 + + in BSB.word32LE w32 <> BSB.word16LE w16 <> BSB.word8 z + + | otherwise = + error "impossible" -- | Decode a 'ByteString', encoded as base32 using the bech32 character -- set, to a base256-encoded 'ByteString'. @@ -198,8 +192,8 @@ decode = handle . go mempty where Nothing -> Nothing Just s -> Just (toStrict s) - go acc bs - | BS.length bs < 8 = do + go acc bs@(BI.PS _ _ l) + | l < 8 = do fin <- finalize bs pure (acc <> fin) | otherwise = case BS.splitAt 8 bs of @@ -208,7 +202,7 @@ decode = handle . go mempty where go (acc <> res) etc finalize :: BS.ByteString -> Maybe BSB.Builder -finalize bs +finalize bs@(BI.PS _ _ l) | l == 0 = Just mempty | otherwise = do guard (l >= 2) @@ -277,7 +271,6 @@ finalize bs | otherwise -> Nothing where - l = BS.length bs word5 :: Int -> Maybe Word8 word5 i = fmap fi (BS.elemIndex (fi (BU.unsafeIndex bs i)) bech32_charset)