{-| Module : Z.Crypto.MPI Description : Multiple Precision Integer Copyright : Dong Han, 2021 License : BSD Maintainer : winterland1989@gmail.com Stability : experimental Portability : non-portable This module provide Botan's Multiple Precision Integer, featuring constant-time operations, which is suit for cryptograph usage. -} module Z.Crypto.MPI ( -- * MPI MPI, fromCInt, toWord32, byteSize, Z.Crypto.MPI.bitSize -- * MPI Builder & Parser , toHex, toDecimal, fromHex, fromDecimal -- * MPI Predicator , isNegative, isZero, isOdd, isEven, isPrim -- * MPI specific , mulMod, powMod, modInverse, Z.Crypto.MPI.gcd -- * Random MPI , randBits, randRange -- * Internal , copyMPI , newMPI , unsafeNewMPI , newMPI' , unsafeNewMPI' , withMPI , unsafeWithMPI ) where import Control.Monad import Data.Bits import qualified Data.Scientific as Scientific import Data.Word import GHC.Exts import GHC.Generics import GHC.Integer.GMP.Internals import GHC.Real import System.IO.Unsafe (unsafeDupablePerformIO) import Z.Botan.Exception import Z.Botan.FFI import Z.Crypto.RNG import qualified Z.Data.Array as A import Z.Data.ASCII import qualified Z.Data.Builder as B import Z.Data.JSON (JSON (..), Value (..), fail', withBoundedScientific) import qualified Z.Data.Parser as P import qualified Z.Data.Text as T import qualified Z.Data.Vector.Base as V import Z.Foreign (CInt, CSize, MutablePrimArray (MutablePrimArray), PrimArray (..), allocPrimUnsafe, newPrimArray, unsafeFreezePrimArray) -- | Opaque Botan Multiple Precision Integers. newtype MPI = MPI BotanStruct instance Eq MPI where {-# INLINE (==) #-} a == b = unsafeWithMPI a $ \ btsa -> withMPI b $ \ btsb -> do r <- botan_mp_equal btsa btsb return $! r == 1 instance Ord MPI where {-# INLINE compare #-} a `compare` b = unsafeWithMPI a $ \ btsa -> withMPI b $ \ btsb -> do (r, _) <- allocPrimUnsafe $ \ r -> botan_mp_cmp r btsa btsb return $! case (r :: CInt) of 1 -> GT 0 -> EQ _ -> LT instance Num MPI where {-# INLINE (+) #-} a + b = unsafeDupablePerformIO $ do withMPI a $ \ btsa -> withMPI b $ \ btsb -> newMPI $ \ btsr -> botan_mp_add btsr btsa btsb {-# INLINE (-) #-} a - b = unsafeDupablePerformIO $ do withMPI a $ \ btsa -> withMPI b $ \ btsb -> newMPI $ \ btsr -> botan_mp_sub btsr btsa btsb {-# INLINE (*) #-} a * b = unsafeDupablePerformIO $ do withMPI a $ \ btsa -> withMPI b $ \ btsb -> newMPI $ \ btsr -> botan_mp_mul btsr btsa btsb {-# INLINE negate #-} negate a = unsafeWithMPI a $ \ btsa -> newMPI (\ bts -> do throwBotanIfMinus_ (botan_mp_set_from_mp bts btsa) botan_mp_flip_sign bts) {-# INLINE abs #-} abs mp | isNegative mp = negate mp | otherwise = mp {-# INLINE signum #-} signum mp = case mp `compare` zero of LT -> -1 EQ -> 0 _ -> 1 {-# INLINE fromInteger #-} fromInteger c | c == 0 = zero | otherwise = unsafeDupablePerformIO $ do mpa@(MutablePrimArray mba#)<- newPrimArray (I# (word2Int# siz#)) void (exportIntegerToMutableByteArray c mba# 0## 1#) (PrimArray ba# :: PrimArray Word8) <- unsafeFreezePrimArray mpa r <- newMPI $ \ bts -> hs_botan_mp_from_bin bts ba# 0 (I# (word2Int# siz#)) return $! if c < 0 then negate r else r where siz# = sizeInBaseInteger c 256# instance Real MPI where {-# INLINE toRational #-} toRational mp = toInteger mp :% 1 instance Enum MPI where succ x = x + 1 pred x = x - 1 toEnum = fromIntegral fromEnum = fromIntegral {-# INLINE enumFrom #-} {-# INLINE enumFromThen #-} {-# INLINE enumFromTo #-} {-# INLINE enumFromThenTo #-} enumFrom x = enumDeltaMPI x 1 enumFromThen x y = enumDeltaMPI x (y-x) enumFromTo x lim = enumDeltaToMPI x 1 lim enumFromThenTo x y lim = enumDeltaToMPI x (y-x) lim instance JSON MPI where {-# INLINE fromValue #-} fromValue = withBoundedScientific "Z.Crypto.MPI.MPI" $ \ n -> case Scientific.floatingOrInteger n :: Either Double Integer of Right x -> pure (fromInteger x) Left _ -> fail' . B.unsafeBuildText $ do "converting Integer failed, unexpected floating number " B.scientific n {-# INLINE toValue #-} toValue = Number . fromIntegral {-# INLINE encodeJSON #-} encodeJSON = toDecimal -- These RULES are copied from GHC.Enum {-# RULES "enumDeltaMPI" [~1] forall x y. enumDeltaMPI x y = build (\c _ -> enumDeltaMPIFB c x y) "efdtMPI" [~1] forall x d l. enumDeltaToMPI x d l = build (\c n -> enumDeltaToMPIFB c n x d l) "efdtMPI1" [~1] forall x l. enumDeltaToMPI x 1 l = build (\c n -> enumDeltaToMPI1FB c n x l) "enumDeltaToMPI1FB" [1] forall c n x. enumDeltaToMPIFB c n x 1 = enumDeltaToMPI1FB c n x "enumDeltaMPI" [1] enumDeltaMPIFB (:) = enumDeltaMPI "enumDeltaToMPI" [1] enumDeltaToMPIFB (:) [] = enumDeltaToMPI "enumDeltaToMPI1" [1] enumDeltaToMPI1FB (:) [] = enumDeltaToMPI1 #-} {-# INLINE [0] enumDeltaMPIFB #-} -- See Note [Inline FB functions] in GHC.List enumDeltaMPIFB :: (MPI -> b -> b) -> MPI -> MPI -> b enumDeltaMPIFB c x0 d = go x0 where go x = x `seq` (x `c` go (x+d)) {-# NOINLINE [1] enumDeltaMPI #-} enumDeltaMPI :: MPI -> MPI -> [MPI] enumDeltaMPI x d = x `seq` (x : enumDeltaMPI (x+d) d) -- strict accumulator, so -- head (drop 1000000 [1 .. ] -- works {-# INLINE [0] enumDeltaToMPIFB #-} -- See Note [Inline FB functions] in GHC.List -- Don't inline this until RULE "enumDeltaToMPI" has had a chance to fire enumDeltaToMPIFB :: (MPI -> a -> a) -> a -> MPI -> MPI -> MPI -> a enumDeltaToMPIFB c n x delta lim | delta >= 0 = up_fb c n x delta lim | otherwise = dn_fb c n x delta lim {-# INLINE [0] enumDeltaToMPI1FB #-} -- See Note [Inline FB functions] in GHC.List -- Don't inline this until RULE "enumDeltaToMPI" has had a chance to fire enumDeltaToMPI1FB :: (MPI -> a -> a) -> a -> MPI -> MPI -> a enumDeltaToMPI1FB c n x0 lim = go (x0 :: MPI) where go x | x > lim = n | otherwise = x `c` go (x+1) {-# NOINLINE [1] enumDeltaToMPI #-} enumDeltaToMPI :: MPI -> MPI -> MPI -> [MPI] enumDeltaToMPI x delta lim | delta >= 0 = up_list x delta lim | otherwise = dn_list x delta lim {-# NOINLINE [1] enumDeltaToMPI1 #-} enumDeltaToMPI1 :: MPI -> MPI -> [MPI] -- Special case for Delta = 1 enumDeltaToMPI1 x0 lim = go (x0 :: MPI) where go x | x > lim = [] | otherwise = x : go (x+1) up_fb :: (MPI -> a -> a) -> a -> MPI -> MPI -> MPI -> a up_fb c n x0 delta lim = go (x0 :: MPI) where go x | x > lim = n | otherwise = x `c` go (x+delta) dn_fb :: (MPI -> a -> a) -> a -> MPI -> MPI -> MPI -> a dn_fb c n x0 delta lim = go (x0 :: MPI) where go x | x < lim = n | otherwise = x `c` go (x+delta) up_list :: MPI -> MPI -> MPI -> [MPI] up_list x0 delta lim = go (x0 :: MPI) where go x | x > lim = [] | otherwise = x : go (x+delta) dn_list :: MPI -> MPI -> MPI -> [MPI] dn_list x0 delta lim = go (x0 :: MPI) where go x | x < lim = [] | otherwise = x : go (x+delta) instance Integral MPI where {-# INLINE quotRem #-} a `quotRem` b = unsafeWithMPI a $ \ btsa -> withMPI b $ \ btsb -> newMPI' $ \ q -> newMPI $ \ r -> botan_mp_div q r btsa btsb {-# INLINE toInteger #-} toInteger mp | isZero mp = 0 | otherwise = unsafeWithMPI mp $ \ bts -> do mpa@(MutablePrimArray mba#) <- newPrimArray siz throwBotanIfMinus_ (hs_botan_mp_to_bin bts mba# 0) (PrimArray ba# :: PrimArray Word8) <- unsafeFreezePrimArray mpa let r = importIntegerFromByteArray ba# 0## (int2Word# siz#) 1# return $! if mp < 0 then negate r else r where !siz@(I# siz#) = (byteSize mp) -- | The 'testBit' implementation ignore sign. instance Bits MPI where x .&. y = fromInteger $ (toInteger x) .&. (toInteger y) {-# INLINE (.&.) #-} x .|. y = fromInteger $ (toInteger x) .|. (toInteger y) {-# INLINE (.|.) #-} x `xor` y = fromInteger $ (toInteger x) `xor` (toInteger y) {-# INLINE xor #-} complement = fromInteger . complement . toInteger {-# INLINE complement #-} shift x i | i >= 0 = unsafeWithMPI x $ \ btsx -> newMPI $ \ btsr -> botan_mp_lshift btsr btsx (fromIntegral i) | otherwise = unsafeWithMPI x $ \ btsx -> newMPI $ \ btsr -> botan_mp_rshift btsr btsx (fromIntegral (-i)) {-# INLINE shift #-} testBit x i = unsafeWithMPI x $ \ btsx -> do r <- botan_mp_get_bit btsx (fromIntegral i) return $! r == 1 {-# INLINE testBit #-} zeroBits = 0 bit = setBit 0 {-# INLINE bit #-} setBit a i = unsafeWithMPI a $ \ btsa -> newMPI (\ btsr -> do throwBotanIfMinus_ (botan_mp_set_from_mp btsr btsa) botan_mp_set_bit btsr (fromIntegral i)) {-# INLINE setBit #-} clearBit a i = unsafeWithMPI a $ \ btsa -> newMPI (\ btsr -> do throwBotanIfMinus_ (botan_mp_set_from_mp btsr btsa) botan_mp_clear_bit btsr (fromIntegral i)) {-# INLINE clearBit #-} popCount = popCount . toInteger {-# INLINE popCount #-} rotate x i = shift x i -- since an MPI never wraps around {-# INLINE rotate #-} bitSizeMaybe _ = Nothing bitSize _ = error "Z.Crypto.MPI.bitSize(MPI)" isSigned _ = True instance Show MPI where show = T.toString instance T.Print MPI where {-# INLINE toUTF8BuilderP #-} toUTF8BuilderP _ = toDecimal zero :: MPI zero = unsafeNewMPI (\ _ -> return ()) newMPI :: (BotanStructT -> IO a) -> IO MPI {-# INLINABLE newMPI #-} newMPI f = do mp <- newBotanStruct (\ bts -> botan_mp_init bts) botan_mp_destroy _ <- withBotanStruct mp f return (MPI mp) newMPI' :: (BotanStructT -> IO a) -> IO (MPI, a) {-# INLINABLE newMPI' #-} newMPI' f = do mp <- newBotanStruct (\ bts -> botan_mp_init bts) botan_mp_destroy r <- withBotanStruct mp f return (MPI mp, r) copyMPI :: MPI -> IO MPI {-# INLINABLE copyMPI #-} copyMPI (MPI a) = do withBotanStruct a $ \ btsa -> do newMPI (\ bts -> botan_mp_set_from_mp bts btsa) withMPI :: MPI -> (BotanStructT -> IO a) -> IO a {-# INLINABLE withMPI #-} withMPI (MPI bts) f = withBotanStruct bts f unsafeWithMPI :: MPI -> (BotanStructT -> IO a) -> a {-# INLINABLE unsafeWithMPI #-} unsafeWithMPI (MPI bts) f = unsafeDupablePerformIO (withBotanStruct bts f) unsafeNewMPI :: (BotanStructT -> IO a) -> MPI {-# INLINABLE unsafeNewMPI #-} unsafeNewMPI f = unsafeDupablePerformIO $ do mp <- newBotanStruct (\ bts -> botan_mp_init bts) botan_mp_destroy _ <- withBotanStruct mp f return (MPI mp) unsafeNewMPI' :: (BotanStructT -> IO a) -> (MPI, a) {-# INLINABLE unsafeNewMPI' #-} unsafeNewMPI' f = unsafeDupablePerformIO $ do mp <- newBotanStruct (\ bts -> botan_mp_init bts) botan_mp_destroy r <- withBotanStruct mp f return (MPI mp, r) -- | Get 'MPI' 's byte size. byteSize :: MPI -> Int {-# INLINABLE byteSize #-} byteSize mp = fromIntegral @CSize . fst . unsafeWithMPI mp $ \ bts -> allocPrimUnsafe (botan_mp_num_bytes bts) -- | Get 'MPI' 's bit size. bitSize :: MPI -> Int {-# INLINABLE bitSize #-} bitSize mp = fromIntegral @CSize . fst . unsafeWithMPI mp $ \ bts -> allocPrimUnsafe (botan_mp_num_bits bts) -- | Set 'MPI' from an integer value. fromCInt :: CInt -> MPI {-# INLINABLE fromCInt #-} fromCInt x = unsafeNewMPI $ \ bts -> botan_mp_set_from_int bts x -- | Convert a MPI to 'Word32', the sign is ignored. toWord32 :: MPI -> Word32 {-# INLINABLE toWord32 #-} toWord32 mp = fst . unsafeWithMPI mp $ \ bts -> allocPrimUnsafe (botan_mp_to_uint32 bts) -- | Write a 'MPI' in decimal format, with negative sign if < 0. toDecimal :: MPI -> B.Builder () {-# INLINABLE toDecimal #-} toDecimal mp = do when (isNegative mp) (B.word8 MINUS) -- botan write \NUL terminator B.ensureN (byteSize mp * 3 + 1) $ \ (MutablePrimArray mba#) off -> withMPI mp $ \ btst -> do hs_botan_mp_to_dec btst mba# off -- | Parse a 'MPI' in decimal format, parse leading minus sign. fromDecimal :: P.Parser MPI {-# INLINABLE fromDecimal #-} fromDecimal = do sign <- P.peek let neg = sign == MINUS when neg P.skipWord8 v@(V.PrimVector (A.PrimArray ba#) s l) <- P.takeWhile1 isDigit let (x, r) = unsafeNewMPI' $ \ bts -> do r' <- hs_botan_mp_set_from_dec bts ba# s l when (r' >= 0 && neg) (void $ botan_mp_flip_sign bts) return r' if (r < 0) then P.fail' $ "wrong decimal integer: " <> T.toText v else return x -- | Write a 'MPI' in hexadecimal format(without '0x' prefix), the sign is ignored. toHex :: MPI -> B.Builder () {-# INLINABLE toHex #-} toHex mp = -- botan write \NUL terminator let !siz = byteSize mp `unsafeShiftL` 1 in B.ensureN (siz + 1) $ \ (MutablePrimArray mba#) off -> withMPI mp $ \ btst -> do _ <- hs_botan_mp_to_hex btst mba# off return (off+siz) -- | Parse a 'MPI' in hexadecimal format(without '0x' prefix), no sign is allowed. fromHex :: P.Parser MPI {-# INLINABLE fromHex #-} fromHex = do v@(V.PrimVector (A.PrimArray ba#) s l) <- P.takeWhile1 isHexDigit let (x, r) = unsafeNewMPI' $ \ bts -> hs_botan_mp_set_from_hex bts ba# s l if (r < 0) then P.fail' $ "wrong hexadecimal integer: " <> T.toText v else return x isNegative :: MPI -> Bool {-# INLINABLE isNegative #-} isNegative mp = unsafeWithMPI mp $ \ bts -> do r <- botan_mp_is_negative bts return $! r == 1 isZero :: MPI -> Bool {-# INLINABLE isZero #-} isZero mp = unsafeWithMPI mp $ \ bts -> do r <- botan_mp_is_zero bts return $! r == 1 isOdd :: MPI -> Bool {-# INLINABLE isOdd #-} isOdd mp = unsafeWithMPI mp $ \ bts -> do r <- botan_mp_is_odd bts return $! r == 1 isEven :: MPI -> Bool {-# INLINABLE isEven #-} isEven mp = unsafeWithMPI mp $ \ bts -> do r <- botan_mp_is_even bts return $! r == 1 -------------------------------------------------------------------------------- -- | mulMod x y mod = x times y modulo mod mulMod :: MPI -> MPI -> MPI -> MPI {-# INLINABLE mulMod #-} mulMod x y m = unsafeNewMPI $ \ btsr -> withMPI x $ \ btsx -> withMPI y $ \ btsy -> withMPI m $ \ btsm -> botan_mp_mod_mul btsr btsx btsy btsm -- | Modular exponentiation. powMod base exp mod = base power exp module mod powMod :: MPI -> MPI -> MPI -> MPI {-# INLINABLE powMod #-} powMod x y m = unsafeNewMPI $ \ btsr -> withMPI x $ \ btsx -> withMPI y $ \ btsy -> withMPI m $ \ btsm -> botan_mp_powmod btsr btsx btsy btsm -- | Modular inverse, find an integer x so that @a⋅x ≡ 1 mod m@ -- -- If no modular inverse exists (for instance because in and modulus are not relatively prime), return 0. modInverse :: MPI -> MPI -> MPI {-# INLINABLE modInverse #-} modInverse x y = unsafeNewMPI $ \ btsr -> withMPI x $ \ btsx -> withMPI y $ \ btsy -> botan_mp_mod_inverse btsr btsx btsy -- | Create a random 'MPI' of the specified bit size. randBits :: HasCallStack => RNG -> Int -> IO MPI {-# INLINABLE randBits #-} randBits rng x = do newMPI $ \ bts -> withRNG rng $ \ bts_rng -> throwBotanIfMinus_ (botan_mp_rand_bits bts bts_rng (fromIntegral (max x 0))) -- | Create a random 'MPI' within the provided range. randRange :: HasCallStack => RNG -> MPI -- ^ lower bound -> MPI -- ^ upper bound -> IO MPI {-# INLINABLE randRange #-} randRange rng lower upper = do newMPI $ \ bts -> withRNG rng $ \ bts_rng -> withMPI lower $ \ bts_lower -> withMPI upper $ \ bts_upper -> throwBotanIfMinus_ (botan_mp_rand_range bts bts_rng bts_lower bts_upper) -- | Compute the greatest common divisor of x and y. gcd :: MPI -> MPI -> MPI {-# INLINABLE gcd #-} gcd x y = unsafeNewMPI $ \ bts -> withMPI x $ \ bts_x -> withMPI y $ \ bts_y -> botan_mp_gcd bts bts_x bts_y -- | Test if n is prime. -- -- The algorithm used (Miller-Rabin) is probabilistic, -- set test_prob to the desired assurance level. -- For example if test_prob is 64, then sufficient Miller-Rabin iterations will run to -- assure there is at most a 1/2**64 chance that n is composite. isPrim :: HasCallStack => RNG -> MPI -> Int -> IO Bool {-# INLINABLE isPrim #-} isPrim rng x prob = do withRNG rng $ \ bts_rng -> withMPI x $ \ bts_x -> do r <- throwBotanIfMinus (botan_mp_is_prime bts_x bts_rng (fromIntegral (max 0 prob))) return $! r == 1