bech32

Pure Haskell bech32 and bech32m encodings.
git clone git://git.ppad.tech/bech32.git
Log | Files | Refs | LICENSE

commit 0559452ec1000ca71597ae7b6521c6e76fde5bc8
parent 1164ff141edfbac279387d804829ee82405f74d5
Author: Jared Tobin <jared@jtobin.io>
Date:   Fri, 13 Dec 2024 06:33:50 -0330

lib: misc reorg/tweaks

Diffstat:
Mbench/Main.hs | 30++++++++++--------------------
Mlib/Data/ByteString/Base32.hs | 55++++++++++++++++++++++++++++---------------------------
Mlib/Data/ByteString/Bech32.hs | 1+
Mppad-bech32.cabal | 10+++++-----
4 files changed, 44 insertions(+), 52 deletions(-)

diff --git a/bench/Main.hs b/bench/Main.hs @@ -3,7 +3,6 @@ module Main where import Criterion.Main -import qualified Data.ByteString.Base32 as Base32 import qualified Data.ByteString.Bech32 as Bech32 main :: IO () @@ -12,24 +11,15 @@ main = defaultMain [ ] suite :: Benchmark -suite = env setup $ \big -> - bgroup "ppad-bech32" [ - bgroup "base32" [ - bench "base32 120b" $ whnf Base32.encode - "jtobin was here" - , bench "base32 128b" $ whnf Base32.encode - "jtobin was here!" - , bench "base32 240b" $ whnf Base32.encode - "jtobin was herejtobin was here" - , bench "base32 1200b" $ whnf Base32.encode big - ] - , bgroup "bech32" [ - bench "bech32 120b" $ nf (Bech32.encode "bc") - "jtobin was here" - , bench "bech32 128b" $ nf (Bech32.encode "bc") - "jtobin was here!" - ] +suite = + bgroup "ppad-bech32" [ + bgroup "bech32" [ + bench "120b" $ nf (Bech32.encode "bc") + "jtobin was here" + , bench "128b (non 40-bit multiple length)" $ nf (Bech32.encode "bc") + "jtobin was here!" + , bench "240b" $ nf (Bech32.encode "bc") + "jtobin was herejtobin was here" ] - where - setup = pure . mconcat . take 10 $ repeat "jtobin was here" + ] diff --git a/lib/Data/ByteString/Base32.hs b/lib/Data/ByteString/Base32.hs @@ -8,7 +8,7 @@ module Data.ByteString.Base32 ( , as_word5 , as_bech32 - -- not base32-related, but convenient to put here + -- not actually base32-related, but convenient to put here , Encoding(..) , create_checksum , verify_checksum @@ -48,37 +48,12 @@ bech32_charset :: BS.ByteString bech32_charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" -- adapted from emilypi's 'base32' library -arrange :: Word32 -> Word32 -> BSB.Builder -arrange w32 w8 = - let mask = 0b00011111 - bech32_char = fi . BS.index bech32_charset . fi - - w8_0 = bech32_char (mask .&. (w32 `B.shiftR` 27)) - w8_1 = bech32_char (mask .&. (w32 `B.shiftR` 22)) - w8_2 = bech32_char (mask .&. (w32 `B.shiftR` 17)) - w8_3 = bech32_char (mask .&. (w32 `B.shiftR` 12)) - w8_4 = bech32_char (mask .&. (w32 `B.shiftR` 07)) - w8_5 = bech32_char (mask .&. (w32 `B.shiftR` 02)) - w8_6 = bech32_char (mask .&. (w32 `B.shiftL` 03 .|. w8 `B.shiftR` 05)) - w8_7 = bech32_char (mask .&. w8) - - w64 = w8_0 - .|. w8_1 `B.shiftL` 8 - .|. w8_2 `B.shiftL` 16 - .|. w8_3 `B.shiftL` 24 - .|. w8_4 `B.shiftL` 32 - .|. w8_5 `B.shiftL` 40 - .|. w8_6 `B.shiftL` 48 - .|. w8_7 `B.shiftL` 56 - - in BSB.word64LE w64 - --- adapted from emilypi's 'base32' library encode :: BS.ByteString -> BS.ByteString 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 @@ -143,6 +118,32 @@ encode dat = toStrict (go dat) where Nothing -> error "impossible, chunk length is 5" Just (word32be -> w32, fi -> w8) -> arrange w32 w8 <> go etc +-- adapted from emilypi's 'base32' library +arrange :: Word32 -> Word32 -> BSB.Builder +arrange w32 w8 = + let mask = 0b00011111 + bech32_char = fi . BS.index bech32_charset . fi + + w8_0 = bech32_char (mask .&. (w32 `B.shiftR` 27)) + w8_1 = bech32_char (mask .&. (w32 `B.shiftR` 22)) + w8_2 = bech32_char (mask .&. (w32 `B.shiftR` 17)) + w8_3 = bech32_char (mask .&. (w32 `B.shiftR` 12)) + w8_4 = bech32_char (mask .&. (w32 `B.shiftR` 07)) + w8_5 = bech32_char (mask .&. (w32 `B.shiftR` 02)) + w8_6 = bech32_char (mask .&. (w32 `B.shiftL` 03 .|. w8 `B.shiftR` 05)) + w8_7 = bech32_char (mask .&. w8) + + w64 = w8_0 + .|. w8_1 `B.shiftL` 8 + .|. w8_2 `B.shiftL` 16 + .|. w8_3 `B.shiftL` 24 + .|. w8_4 `B.shiftL` 32 + .|. w8_5 `B.shiftL` 40 + .|. w8_6 `B.shiftL` 48 + .|. w8_7 `B.shiftL` 56 + + in BSB.word64LE w64 + -- naive base32 -> word5 as_word5 :: BS.ByteString -> BS.ByteString as_word5 = BS.map f where diff --git a/lib/Data/ByteString/Bech32.hs b/lib/Data/ByteString/Bech32.hs @@ -20,6 +20,7 @@ toStrict = BS.toStrict verify_checksum :: BS.ByteString -> BS.ByteString -> Bool verify_checksum = B32.verify_checksum Bech32 +-- XX no need for this to be here create_checksum :: BS.ByteString -> BS.ByteString -> BS.ByteString create_checksum = B32.create_checksum Bech32 diff --git a/ppad-bech32.cabal b/ppad-bech32.cabal @@ -1,7 +1,7 @@ cabal-version: 3.0 name: ppad-bech32 version: 0.1.0 -synopsis: The bech32m encoding, per BIP350. +synopsis: The bech32 and bech32m encoding, per BIPs 173 & 350. license: MIT license-file: LICENSE author: Jared Tobin @@ -11,8 +11,7 @@ build-type: Simple tested-with: GHC == 9.8.1 extra-doc-files: CHANGELOG description: - A pure implementation of bech32m encoding & decoding on strict and - lazy ByteStrings. + BIP173 bech32 & BIP350 bech32m encoding/decoding on strict ByteStrings. source-repository head type: git @@ -23,9 +22,10 @@ library hs-source-dirs: lib ghc-options: -Wall - exposed-modules: + other-modules: Data.ByteString.Base32 - , Data.ByteString.Bech32 + exposed-modules: + Data.ByteString.Bech32 , Data.ByteString.Bech32m build-depends: base >= 4.9 && < 5