diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,8 @@
 # Changelog
 
+- 0.2.3 (2025-06-21)
+  * Minor performance improvements and internal housekeeping.
+
 - 0.2.2 (2025-02-28)
   * Various optimizations, including roughly 2x faster decoding
     performance.
diff --git a/lib/Data/ByteString/Bech32.hs b/lib/Data/ByteString/Bech32.hs
--- a/lib/Data/ByteString/Bech32.hs
+++ b/lib/Data/ByteString/Bech32.hs
@@ -50,7 +50,8 @@
   -> Maybe BS.ByteString  -- ^ bech32-encoded bytestring
 encode (B8.map C.toLower -> hrp) (B32.encode -> dat) = do
   guard (BI.valid_hrp hrp)
-  let check = create_checksum hrp (BI.as_word5 dat)
+  ws <- BI.as_word5 dat
+  let check = create_checksum hrp ws
       res = toStrict $
            BSB.byteString hrp
         <> BSB.word8 49 -- 1
diff --git a/lib/Data/ByteString/Bech32/Internal.hs b/lib/Data/ByteString/Bech32/Internal.hs
--- a/lib/Data/ByteString/Bech32/Internal.hs
+++ b/lib/Data/ByteString/Bech32/Internal.hs
@@ -21,7 +21,7 @@
 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 (Word32)
+import Data.Word (Word8, Word32)
 
 fi :: (Integral a, Num b) => a -> b
 fi = fromIntegral
@@ -39,14 +39,53 @@
 bech32_charset :: BS.ByteString
 bech32_charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
 
--- naive base32 -> word5
-as_word5 :: BS.ByteString -> BS.ByteString
-as_word5 = BS.map f where
-  f b = case BS.elemIndex (fi b) bech32_charset of
-    Nothing -> error "ppad-bech32 (as_word5): input not bech32-encoded"
-    Just w -> fi w
+word5 :: Word8 -> Maybe Word8
+word5 = \case
+  113 -> pure $! 00 -- 'q'
+  112 -> pure $! 01 -- 'p'
+  122 -> pure $! 02 -- 'z'
+  114 -> pure $! 03 -- 'r'
+  121 -> pure $! 04 -- 'y'
+  57  -> pure $! 05 -- '9'
+  120 -> pure $! 06 -- 'x'
+  56  -> pure $! 07 -- '8'
+  103 -> pure $! 08 -- 'g'
+  102 -> pure $! 09 -- 'f'
+  50  -> pure $! 10 -- '2'
+  116 -> pure $! 11 -- 't'
+  118 -> pure $! 12 -- 'v'
+  100 -> pure $! 13 -- 'd'
+  119 -> pure $! 14 -- 'w'
+  48  -> pure $! 15 -- '0'
+  115 -> pure $! 16 -- 's'
+  51  -> pure $! 17 -- '3'
+  106 -> pure $! 18 -- 'j'
+  110 -> pure $! 19 -- 'n'
+  53  -> pure $! 20 -- '5'
+  52  -> pure $! 21 -- '4'
+  107 -> pure $! 22 -- 'k'
+  104 -> pure $! 23 -- 'h'
+  99  -> pure $! 24 -- 'c'
+  101 -> pure $! 25 -- 'e'
+  54  -> pure $! 26 -- '6'
+  109 -> pure $! 27 -- 'm'
+  117 -> pure $! 28 -- 'u'
+  97  -> pure $! 29 -- 'a'
+  55  -> pure $! 30 -- '7'
+  108 -> pure $! 31 -- 'l'
+  _   -> Nothing
+{-# INLINE word5 #-}
 
--- naive word5 -> base32
+-- base32 -> word5
+as_word5 :: BS.ByteString -> Maybe BS.ByteString
+as_word5 = go mempty where
+  go acc bs = case BS.uncons bs of
+    Nothing -> pure (toStrict acc)
+    Just (h, t) -> do
+      w5 <- word5 (fi h)
+      go (acc <> BSB.word8 w5) t
+
+-- word5 -> base32
 as_base32 :: BS.ByteString -> BS.ByteString
 as_base32 = BS.map (BU.unsafeIndex bech32_charset . fi)
 
@@ -107,8 +146,12 @@
   Nothing  -> False
   Just idx ->
     let (hrp, BU.unsafeDrop 1 -> dat) = BS.splitAt idx b32
-        bs = hrp_expand hrp <> as_word5 dat
-    in  polymod bs == case enc of
-          Bech32 -> 1
-          Bech32m -> _BECH32M_CONST
+        w5s = as_word5 dat
+    in  case w5s of
+          Nothing -> False
+          Just ws ->
+            let bs = hrp_expand hrp <> ws
+            in  polymod bs == case enc of
+                  Bech32 -> 1
+                  Bech32m -> _BECH32M_CONST
 
diff --git a/lib/Data/ByteString/Bech32m.hs b/lib/Data/ByteString/Bech32m.hs
--- a/lib/Data/ByteString/Bech32m.hs
+++ b/lib/Data/ByteString/Bech32m.hs
@@ -51,7 +51,8 @@
   -> Maybe BS.ByteString  -- ^ bech32m-encoded bytestring
 encode (B8.map C.toLower -> hrp) (B32.encode -> dat) = do
   guard (BI.valid_hrp hrp)
-  let check = create_checksum hrp (BI.as_word5 dat)
+  ws <- BI.as_word5 dat
+  let check = create_checksum hrp ws
       res = toStrict $
            BSB.byteString hrp
         <> BSB.word8 49 -- 1
diff --git a/ppad-bech32.cabal b/ppad-bech32.cabal
--- a/ppad-bech32.cabal
+++ b/ppad-bech32.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               ppad-bech32
-version:            0.2.2
+version:            0.2.3
 synopsis:           bech32 and bech32m encoding/decoding, per BIPs 173 & 350.
 license:            MIT
 license-file:       LICENSE
