packages feed

ppad-bech32-0.2.6: lib/Data/ByteString/Bech32/Internal.hs

{-# OPTIONS_HADDOCK hide, prune #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ViewPatterns #-}

module Data.ByteString.Bech32.Internal (
    as_word5
  , as_base32
  , Encoding(..)
  , create_checksum
  , verify
  , valid_hrp
  , encode
  , decode
  ) where

import Control.Monad (guard)
import Data.Bits ((.&.), (.|.))
import qualified Data.Bits as B
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base32 as B32
import Data.ByteString.Base32.Internal
  (enc_tab, dec_tab, w5_tab, encode_with)
import qualified Data.ByteString.Internal as BI
import qualified Data.ByteString.Unsafe as BU
import Data.Word (Word8, Word32)
import Foreign.ForeignPtr (withForeignPtr)
import Foreign.Ptr (Ptr, plusPtr)
import Foreign.Storable (peekElemOff, pokeElemOff)
import System.IO.Unsafe (unsafeDupablePerformIO)

fi :: (Integral a, Num b) => a -> b
fi = fromIntegral
{-# INLINE fi #-}

_BECH32M_CONST :: Word32
_BECH32M_CONST = 0x2bc830a3

-- | Translate base32 bytestring to its 5-bit-value bytestring.  Each
--   input byte is looked up in 'dec_tab'; if any byte is not a valid
--   bech32 char, returns 'Nothing'.
as_word5 :: BS.ByteString -> Maybe BS.ByteString
as_word5 (BI.PS sfp soff l) = case dec_tab of
  BI.PS tfp toff _ -> unsafeDupablePerformIO $ do
    fp <- BI.mallocByteString l
    ok <- withForeignPtr fp  $ \dst ->
          withForeignPtr sfp $ \sp0 ->
          withForeignPtr tfp $ \tp0 -> do
            let !sp = sp0 `plusPtr` soff :: Ptr Word8
                !tp = tp0 `plusPtr` toff :: Ptr Word8
                loop !i !acc
                  | i == l    = pure $! acc .&. 0x40 == 0
                  | otherwise = do
                      c <- peekElemOff sp i
                      n <- peekElemOff tp (fi c)
                      pokeElemOff dst i (n .&. 0x1f)
                      loop (i + 1) (acc .|. n)
            loop 0 0
    pure $! if ok then Just (BI.PS fp 0 l) else Nothing

-- | Translate a 5-bit-value bytestring to its bech32 base32
--   bytestring.  Only the low 5 bits of each input byte are used.
as_base32 :: BS.ByteString -> BS.ByteString
as_base32 (BI.PS sfp soff l) = case enc_tab of
  BI.PS tfp toff _ ->
    BI.unsafeCreate l $ \dst ->
      withForeignPtr sfp $ \sp0 ->
      withForeignPtr tfp $ \tp0 -> do
        let !sp = sp0 `plusPtr` soff :: Ptr Word8
            !tp = tp0 `plusPtr` toff :: Ptr Word8
            loop !i
              | i == l    = pure ()
              | otherwise = do
                  v <- peekElemOff sp i
                  c <- peekElemOff tp (fi (v .&. 0x1f))
                  pokeElemOff dst i c
                  loop (i + 1)
        loop 0

polymod :: BS.ByteString -> Word32
polymod = BS.foldl' alg 1 where
  alg !chk v =
    let !b = chk `B.shiftR` 25
        !c = (chk .&. 0x1ffffff) `B.shiftL` 5 `B.xor` fi v
    in  c `B.xor` gen b 0 0x3b6a57b2
          `B.xor` gen b 1 0x26508e6d
          `B.xor` gen b 2 0x1ea119fa
          `B.xor` gen b 3 0x3d4233dd
          `B.xor` gen b 4 0x2a1462b3

  -- the generator 'g' if bit 'i' of 'b' is set, else zero
  gen :: Word32 -> Int -> Word32 -> Word32
  gen b i g
    | B.testBit b i = g
    | otherwise     = 0
  {-# INLINE gen #-}

valid_hrp :: BS.ByteString -> Bool
valid_hrp hrp@(BI.PS _ _ l)
  | l == 0 || l > 83 = False
  | otherwise = BS.all (\b -> (b > 32) && (b < 127)) hrp

-- | Build the bech32 HRP expansion: high-5-bits of each HRP byte,
--   then a single 0, then low-5-bits of each HRP byte.
hrp_expand :: BS.ByteString -> BS.ByteString
hrp_expand (BI.PS sfp soff l) =
  BI.unsafeCreate (2 * l + 1) $ \dst ->
    withForeignPtr sfp $ \sp0 -> do
      let !sp = sp0 `plusPtr` soff :: Ptr Word8
          loop_hi !i
            | i == l    = pure ()
            | otherwise = do
                c <- peekElemOff sp i
                pokeElemOff dst i (c `B.shiftR` 5)
                loop_hi (i + 1)
          loop_lo !i
            | i == l    = pure ()
            | otherwise = do
                c <- peekElemOff sp i
                pokeElemOff dst (l + 1 + i) (c .&. 0x1f)
                loop_lo (i + 1)
      loop_hi 0
      pokeElemOff dst l (0 :: Word8)
      loop_lo 0

data Encoding =
    Bech32
  | Bech32m

zero6 :: BS.ByteString
zero6 = BS.replicate 6 0
{-# NOINLINE zero6 #-}

create_checksum
  :: Encoding -> BS.ByteString -> BS.ByteString -> BS.ByteString
create_checksum enc hrp dat =
  let !pay = BS.concat [hrp_expand hrp, dat, zero6]
      !pm  = polymod pay `B.xor` case enc of
        Bech32  -> 1
        Bech32m -> _BECH32M_CONST
  in  BI.unsafeCreate 6 $ \dst -> do
        pokeElemOff dst 0 (fi (pm `B.shiftR` 25) .&. 0x1f :: Word8)
        pokeElemOff dst 1 (fi (pm `B.shiftR` 20) .&. 0x1f :: Word8)
        pokeElemOff dst 2 (fi (pm `B.shiftR` 15) .&. 0x1f :: Word8)
        pokeElemOff dst 3 (fi (pm `B.shiftR` 10) .&. 0x1f :: Word8)
        pokeElemOff dst 4 (fi (pm `B.shiftR`  5) .&. 0x1f :: Word8)
        pokeElemOff dst 5 (fi  pm               .&. 0x1f :: Word8)

-- | Lowercase an all-uppercase string.  Mixed-case strings (some
--   ASCII uppercase and some ASCII lowercase characters) are rejected,
--   per BIP173.
normalize_case :: BS.ByteString -> Maybe BS.ByteString
normalize_case bs
  | has_upper && BS.any is_lower bs = Nothing
  | has_upper = Just $! BS.map to_lower bs
  | otherwise = Just bs
  where
    has_upper = BS.any is_upper bs

is_upper :: Word8 -> Bool
is_upper c = c >= 0x41 && c <= 0x5A
{-# INLINE is_upper #-}

is_lower :: Word8 -> Bool
is_lower c = c >= 0x61 && c <= 0x7A
{-# INLINE is_lower #-}

to_lower :: Word8 -> Word8
to_lower c
  | is_upper c = c + 0x20
  | otherwise  = c
{-# INLINE to_lower #-}

-- | Does a lowercase hrp and data part (5-bit values plus a 6-value
--   checksum, as bech32 characters) carry a valid checksum?
valid_checksum :: Encoding -> BS.ByteString -> BS.ByteString -> Bool
valid_checksum enc hrp dat
  | BS.length dat < 6 = False
  | otherwise = case as_word5 dat of
      Nothing -> False
      Just ws -> polymod (hrp_expand hrp <> ws) == case enc of
        Bech32  -> 1
        Bech32m -> _BECH32M_CONST

-- | Verify that a bech32 or bech32m string has a valid checksum.
--   All-uppercase input is accepted; mixed-case input is not.
verify :: Encoding -> BS.ByteString -> Bool
verify enc b32 = case normalize_case b32 of
  Nothing -> False
  Just bs -> case BS.elemIndexEnd 0x31 bs of
    Nothing  -> False
    Just idx ->
      let (hrp, BU.unsafeDrop 1 -> dat) = BS.splitAt idx bs
      in  valid_checksum enc hrp dat

-- | Encode a human-readable part and base256 data as bech32 or
--   bech32m.  The human-readable part is lowercased.
encode
  :: Encoding
  -> BS.ByteString        -- ^ base256-encoded human-readable part
  -> BS.ByteString        -- ^ base256-encoded data part
  -> Maybe BS.ByteString  -- ^ bech32-encoded bytestring
encode enc (BS.map to_lower -> hrp) dat = do
  guard (valid_hrp hrp)
  let !ws = encode_with w5_tab dat
  guard (BS.length hrp + BS.length ws + 7 <= 90)
  let !chk = create_checksum enc hrp ws
  pure $! BS.concat [hrp, BS.singleton 0x31, as_base32 ws, as_base32 chk]

-- | Decode a bech32 or bech32m string into its (lowercase)
--   human-readable part and base256 data part.  All-uppercase input
--   is accepted; mixed-case input is not.
decode
  :: Encoding
  -> BS.ByteString                        -- ^ bech32-encoded bytestring
  -> Maybe (BS.ByteString, BS.ByteString) -- ^ (hrp, data less checksum)
decode enc b32 = do
  guard (BS.length b32 <= 90)
  bs  <- normalize_case b32
  sep <- BS.elemIndexEnd 0x31 bs
  let (hrp, BU.unsafeDrop 1 -> raw) = BS.splitAt sep bs
  guard (valid_hrp hrp)
  guard (valid_checksum enc hrp raw)
  dat <- B32.decode (BS.dropEnd 6 raw)
  pure (hrp, dat)