Z-Botan-0.1.1.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
( -- * RNG
MPI, fromCInt, toWord32, byteSize, Z.Crypto.MPI.bitSize
-- * Builder & Parser
, toHex, toDecimal, fromHex, fromDecimal
-- * 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
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)
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
copyMPI (MPI a) = do
withBotanStruct a $ \ btsa -> do
newMPI (\ bts -> botan_mp_set_from_mp bts btsa)
withMPI :: MPI -> (BotanStructT -> IO a) -> IO a
withMPI (MPI bts) f = withBotanStruct bts f
unsafeWithMPI :: MPI -> (BotanStructT -> IO a) -> a
unsafeWithMPI (MPI bts) f = unsafeDupablePerformIO (withBotanStruct bts f)
unsafeNewMPI :: (BotanStructT -> IO a) -> MPI
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)
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
byteSize mp = fromIntegral @CSize . fst . unsafeWithMPI mp $ \ bts ->
allocPrimUnsafe (botan_mp_num_bytes bts)
-- | Get 'MPI' 's bit size.
bitSize :: MPI -> Int
bitSize mp = fromIntegral @CSize . fst . unsafeWithMPI mp $ \ bts ->
allocPrimUnsafe (botan_mp_num_bits bts)
-- | Set 'MPI' from an integer value.
fromCInt :: CInt -> MPI
fromCInt x = unsafeNewMPI $ \ bts ->
botan_mp_set_from_int bts x
-- | Convert a MPI to 'Word32', the sign is ignored.
toWord32 :: HasCallStack => MPI -> Word32
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 ()
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
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 ()
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
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
isNegative mp = unsafeWithMPI mp $ \ bts -> do
r <- botan_mp_is_negative bts
return $! r == 1
isZero :: MPI -> Bool
isZero mp = unsafeWithMPI mp $ \ bts -> do
r <- botan_mp_is_zero bts
return $! r == 1
isOdd :: MPI -> Bool
isOdd mp = unsafeWithMPI mp $ \ bts -> do
r <- botan_mp_is_odd bts
return $! r == 1
isEven :: MPI -> Bool
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
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
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
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 :: RNG -> Int -> IO MPI
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 :: RNG
-> MPI -- ^ lower bound
-> MPI -- ^ upper bound
-> IO MPI
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
gcd x y = unsafeNewMPI $ \ bts ->
withMPI x $ \ bts_x ->
withMPI y $ \ bts_y ->
throwBotanIfMinus_ (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 :: RNG -> MPI -> Int -> IO Bool
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