ppad-bech32-0.2.3: lib/Data/ByteString/Bech32/Internal.hs
{-# OPTIONS_HADDOCK hide, prune #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BinaryLiterals #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
module Data.ByteString.Bech32.Internal (
as_word5
, as_base32
, Encoding(..)
, create_checksum
, verify
, valid_hrp
) where
import Data.Bits ((.&.))
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)
fi :: (Integral a, Num b) => a -> b
fi = fromIntegral
{-# INLINE fi #-}
-- realization for small builders
toStrict :: BSB.Builder -> BS.ByteString
toStrict = BS.toStrict
. BE.toLazyByteStringWith (BE.safeStrategy 128 BE.smallChunkSize) mempty
{-# INLINE toStrict #-}
_BECH32M_CONST :: Word32
_BECH32M_CONST = 0x2bc830a3
bech32_charset :: BS.ByteString
bech32_charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
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 #-}
-- 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)
polymod :: BS.ByteString -> Word32
polymod = BS.foldl' alg 1 where
generator :: Int -> Word32
generator = \case
0 -> 0x3b6a57b2
1 -> 0x26508e6d
2 -> 0x1ea119fa
3 -> 0x3d4233dd
4 -> 0x2a1462b3
_ -> error "ppad-bech32: internal error (please report this as a bug!)"
alg !chk v =
let !b = chk `B.shiftR` 25
c = (chk .&. 0x1ffffff) `B.shiftL` 5 `B.xor` fi v
in loop_gen 0 b c
loop_gen i b !chk
| i > 4 = chk
| otherwise =
let sor | B.testBit (b `B.shiftR` i) 0 = generator i
| otherwise = 0
in loop_gen (succ i) b (chk `B.xor` sor)
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
hrp_expand :: BS.ByteString -> BS.ByteString
hrp_expand bs = toStrict
$ BSB.byteString (BS.map (`B.shiftR` 5) bs)
<> BSB.word8 0
<> BSB.byteString (BS.map (.&. 0b11111) bs)
data Encoding =
Bech32
| Bech32m
create_checksum :: Encoding -> BS.ByteString -> BS.ByteString -> BS.ByteString
create_checksum enc hrp dat =
let pre = hrp_expand hrp <> dat
pay = toStrict $
BSB.byteString pre
<> BSB.byteString "\NUL\NUL\NUL\NUL\NUL\NUL"
pm = polymod pay `B.xor` case enc of
Bech32 -> 1
Bech32m -> _BECH32M_CONST
code i = (fi (pm `B.shiftR` fi i) .&. 0b11111)
in BS.map code "\EM\DC4\SI\n\ENQ\NUL" -- BS.pack [25, 20, 15, 10, 5, 0]
verify :: Encoding -> BS.ByteString -> Bool
verify enc b32 = case BS.elemIndexEnd 0x31 b32 of
Nothing -> False
Just idx ->
let (hrp, BU.unsafeDrop 1 -> dat) = BS.splitAt idx b32
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