Bech32.hs (5882B)
1 {-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-} 2 3 -- official BIP173 reference 4 -- https://github.com/sipa/bech32/tree/master/ref/haskell 5 module Reference.Bech32 6 ( bech32Encode 7 , bech32Decode 8 , toBase32 9 , toBase256 10 , segwitEncode 11 , segwitDecode 12 , Word5() 13 , word5 14 , fromWord5 15 ) where 16 17 import Control.Monad (guard) 18 import qualified Data.Array as Arr 19 import Data.Bits (Bits, unsafeShiftL, unsafeShiftR, (.&.), (.|.), xor, testBit) 20 import qualified Data.ByteString as BS 21 import qualified Data.ByteString.Char8 as BSC 22 import Data.Char (toLower, toUpper) 23 import Data.Foldable (foldl') 24 import Data.Functor.Identity (Identity, runIdentity) 25 import Data.Ix (Ix(..)) 26 import Data.Word (Word8) 27 28 type HRP = BS.ByteString 29 type Data = [Word8] 30 31 (.>>.), (.<<.) :: Bits a => a -> Int -> a 32 (.>>.) = unsafeShiftR 33 (.<<.) = unsafeShiftL 34 35 newtype Word5 = UnsafeWord5 Word8 36 deriving (Eq, Ord) 37 38 instance Ix Word5 where 39 range (UnsafeWord5 m, UnsafeWord5 n) = map UnsafeWord5 $ range (m, n) 40 index (UnsafeWord5 m, UnsafeWord5 n) (UnsafeWord5 i) = index (m, n) i 41 inRange (m,n) i = m <= i && i <= n 42 43 word5 :: Integral a => a -> Word5 44 word5 x = UnsafeWord5 ((fromIntegral x) .&. 31) 45 {-# INLINE word5 #-} 46 {-# SPECIALIZE INLINE word5 :: Word8 -> Word5 #-} 47 48 fromWord5 :: Num a => Word5 -> a 49 fromWord5 (UnsafeWord5 x) = fromIntegral x 50 {-# INLINE fromWord5 #-} 51 {-# SPECIALIZE INLINE fromWord5 :: Word5 -> Word8 #-} 52 53 charset :: Arr.Array Word5 Char 54 charset = Arr.listArray (UnsafeWord5 0, UnsafeWord5 31) "qpzry9x8gf2tvdw0s3jn54khce6mua7l" 55 56 charsetMap :: Char -> Maybe Word5 57 charsetMap c | inRange (Arr.bounds inv) upperC = inv Arr.! upperC 58 | otherwise = Nothing 59 where 60 upperC = toUpper c 61 inv = Arr.listArray ('0', 'Z') (repeat Nothing) Arr.// (map swap (Arr.assocs charset)) 62 swap (a, b) = (toUpper b, Just a) 63 64 bech32Polymod :: [Word5] -> Word 65 bech32Polymod values = foldl' go 1 values .&. 0x3fffffff 66 where 67 go chk value = foldl' xor chk' [g | (g, i) <- zip generator [25..], testBit chk i] 68 where 69 generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] 70 chk' = chk .<<. 5 `xor` (fromWord5 value) 71 72 bech32HRPExpand :: HRP -> [Word5] 73 bech32HRPExpand hrp = map (UnsafeWord5 . (.>>. 5)) (BS.unpack hrp) ++ [UnsafeWord5 0] ++ map word5 (BS.unpack hrp) 74 75 bech32CreateChecksum :: HRP -> [Word5] -> [Word5] 76 bech32CreateChecksum hrp dat = [word5 (polymod .>>. i) | i <- [25,20..0]] 77 where 78 values = bech32HRPExpand hrp ++ dat 79 polymod = bech32Polymod (values ++ map UnsafeWord5 [0, 0, 0, 0, 0, 0]) `xor` 1 80 81 bech32VerifyChecksum :: HRP -> [Word5] -> Bool 82 bech32VerifyChecksum hrp dat = bech32Polymod (bech32HRPExpand hrp ++ dat) == 1 83 84 bech32Encode :: HRP -> [Word5] -> Maybe BS.ByteString 85 bech32Encode hrp dat = do 86 guard $ checkHRP hrp 87 let dat' = dat ++ bech32CreateChecksum hrp dat 88 rest = map (charset Arr.!) dat' 89 result = BSC.concat [BSC.map toLower hrp, BSC.pack "1", BSC.pack rest] 90 guard $ BS.length result <= 90 91 return result 92 93 checkHRP :: BS.ByteString -> Bool 94 checkHRP hrp = not (BS.null hrp) && BS.all (\char -> char >= 33 && char <= 126) hrp 95 96 bech32Decode :: BS.ByteString -> Maybe (HRP, [Word5]) 97 bech32Decode bech32 = do 98 guard $ BS.length bech32 <= 90 99 guard $ BSC.map toUpper bech32 == bech32 || BSC.map toLower bech32 == bech32 100 let (hrp, dat) = BSC.breakEnd (== '1') $ BSC.map toLower bech32 101 guard $ BS.length dat >= 6 102 hrp' <- BSC.stripSuffix (BSC.pack "1") hrp 103 guard $ checkHRP hrp' 104 dat' <- mapM charsetMap $ BSC.unpack dat 105 guard $ bech32VerifyChecksum hrp' dat' 106 return (hrp', take (BS.length dat - 6) dat') 107 108 type Pad f = Int -> Int -> Word -> [[Word]] -> f [[Word]] 109 110 yesPadding :: Pad Identity 111 yesPadding _ 0 _ result = return result 112 yesPadding _ _ padValue result = return $ [padValue] : result 113 {-# INLINE yesPadding #-} 114 115 noPadding :: Pad Maybe 116 noPadding frombits bits padValue result = do 117 guard $ bits < frombits && padValue == 0 118 return result 119 {-# INLINE noPadding #-} 120 121 -- Big endian conversion of a bytestring from base 2^frombits to base 2^tobits. 122 -- frombits and twobits must be positive and 2^frombits and 2^tobits must be smaller than the size of Word. 123 -- Every value in dat must be strictly smaller than 2^frombits. 124 convertBits :: Functor f => [Word] -> Int -> Int -> Pad f -> f [Word] 125 convertBits dat frombits tobits pad = fmap (concat . reverse) $ go dat 0 0 [] 126 where 127 go [] acc bits result = 128 let padValue = (acc .<<. (tobits - bits)) .&. maxv 129 in pad frombits bits padValue result 130 go (value:dat') acc bits result = go dat' acc' (bits' `rem` tobits) (result':result) 131 where 132 acc' = (acc .<<. frombits) .|. fromIntegral value 133 bits' = bits + frombits 134 result' = [(acc' .>>. b) .&. maxv | b <- [bits'-tobits,bits'-2*tobits..0]] 135 maxv = (1 .<<. tobits) - 1 136 {-# INLINE convertBits #-} 137 138 toBase32 :: [Word8] -> [Word5] 139 toBase32 dat = map word5 $ runIdentity $ convertBits (map fromIntegral dat) 8 5 yesPadding 140 141 toBase256 :: [Word5] -> Maybe [Word8] 142 toBase256 dat = fmap (map fromIntegral) $ convertBits (map fromWord5 dat) 5 8 noPadding 143 144 segwitCheck :: Word8 -> Data -> Bool 145 segwitCheck witver witprog = 146 witver <= 16 && 147 if witver == 0 148 then length witprog == 20 || length witprog == 32 149 else length witprog >= 2 && length witprog <= 40 150 151 segwitDecode :: HRP -> BS.ByteString -> Maybe (Word8, Data) 152 segwitDecode hrp addr = do 153 (hrp', dat) <- bech32Decode addr 154 guard $ (hrp == hrp') && not (null dat) 155 let (UnsafeWord5 witver : datBase32) = dat 156 decoded <- toBase256 datBase32 157 guard $ segwitCheck witver decoded 158 return (witver, decoded) 159 160 segwitEncode :: HRP -> Word8 -> Data -> Maybe BS.ByteString 161 segwitEncode hrp witver witprog = do 162 guard $ segwitCheck witver witprog 163 bech32Encode hrp $ UnsafeWord5 witver : toBase32 witprog