Z-Botan-0.2.0.0: Z/Crypto/MPI.hs
{-|
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