packages feed

botan-low-0.2.0.0: src/Botan/Low/MPI.hs

{-|
Module      : Botan.Low.MPI
Description : Multiple Precision Integers
Copyright   : (c) 2023-2024, Apotheca Labs
              (c) 2024-2025, Haskell Foundation
License     : BSD-3-Clause
Maintainer  : joris@well-typed.com, leo@apotheca.io
Stability   : experimental
Portability : POSIX
-}

module Botan.Low.MPI (

    MP(..)
  , withMP
  , mpInit
  , mpDestroy
  , mpToHex
  , mpToStr
  , mpClear
  , mpSetFromInt
  , mpSetFromMP
  , mpCopy
  , mpSetFromStr
  , mpSetFromRadixStr
  , mpNumBits
  , mpNumBytes
  , mpToBin
  , mpFromBin
  , mpToWord32
  , mpIsPositive
  , mpIsNegative
  , mpFlipSign
  , mpIsZero
  , mpAddWord32
  , mpSubWord32
  , mpAdd
  , mpSub
  , mpMul
  , mpDiv
  , mpModMul
  , mpEqual
  , mpCmp
  , mpSwap
  , mpPowMod
  , mpLeftShift
  , mpRightShift
  , mpModInverse
  , mpRandBits
  , mpRandRange
  , mpGCD
  , mpIsPrime
  , mpGetBit
  , mpSetBit
  , mpClearBit

  ) where

import           Botan.Bindings.ConstPtr (ConstPtr (..))
import           Botan.Bindings.MPI
import           Botan.Low.Error.Internal
import           Botan.Low.Internal.ByteString
import           Botan.Low.Make
import           Botan.Low.Remake
import           Botan.Low.RNG
import           Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString
import           Data.Word
import           Foreign.C.Types
import           Foreign.ForeignPtr
import           Foreign.Marshal.Alloc
import           Foreign.Ptr
import           Foreign.Storable

-- Yes, the module is named MPI, but the type is MP.
-- I'm probably renaming the module / type to `Botan.Integer` for ergonomics,
--  like I did with `Botan.RNG`.

-- NOTE: Operations have a different format here, compared to other botan objects.
--  Botan.Make does not apply very well.
--  MPI are rarely mutated, and usually take a destination argument instead.
--  As such, there are some mk- functions specific to MPI, and this module
--  will be greatly improved by idiomatic bindings wrappers

-- NOTE: This whole module is not idiomatic - some methods mutate, some have a destination argument
--  It will need furter wrapping.

newtype MP = MkMP { foreignPtr :: ForeignPtr BotanMPStruct }

withMP     :: MP -> (BotanMP -> IO a) -> IO a
mpDestroy  :: MP -> IO ()
createMP   :: (Ptr BotanMP -> IO CInt) -> IO MP
(withMP, mpDestroy, createMP)
    = mkBindings
        MkBotanMP (.ptr)
        MkMP (.foreignPtr)
        botan_mp_destroy

mpInit :: IO MP
-- mpInit = mkInit MkMP botan_mp_init botan_mp_destroy
mpInit = createMP botan_mp_init

-- NOTE: The actual botan_mp_to_hex is misleading
--  The actual buffer size is 2 + (num_bytes * 2) + 1 bytes in length
--  The leading 2 is `0x` prefix, the trailing 1 is `\0` suffix
mpToHex :: MP -> IO ByteString
mpToHex mp = withMP mp $ \ mpPtr -> do
    numBytes <- mpNumBytes mp
    allocaBytes (2 + (numBytes * 2) + 1) $ \ bytesPtr -> do
        throwBotanIfNegative_ $ botan_mp_to_hex mpPtr bytesPtr
        ByteString.packCString bytesPtr

mpToStr :: MP -> Int -> IO ByteString
mpToStr mp base = withMP mp $ \ mpPtr -> do
    allocBytesQueryingCString $ \ bytesPtr szPtr ->
        botan_mp_to_str mpPtr (fromIntegral base) bytesPtr szPtr

mpClear :: MP -> IO ()
mpClear = mkAction withMP botan_mp_clear

mpSetFromInt :: MP -> Int -> IO ()
mpSetFromInt = mkSetCInt withMP botan_mp_set_from_int

mpSetFromMP :: MP -> MP -> IO ()
mpSetFromMP = mkUnaryOp withMP botan_mp_set_from_mp

-- NOTE: Convenience function
mpCopy :: MP -> IO MP
mpCopy mp = do
    copy <- mpInit
    mpSetFromMP copy mp
    return copy

mpSetFromStr :: MP -> ByteString -> IO ()
mpSetFromStr = mkSetCString withMP (\ mp cstr -> botan_mp_set_from_str mp (ConstPtr cstr))

-- NOTE: According to unit tests, this function *does not* prepend "0x" to the value
mpSetFromRadixStr :: MP -> ByteString -> Int -> IO ()
mpSetFromRadixStr = mkSetCString_csize withMP (\ mp cstr radix -> botan_mp_set_from_radix_str mp (ConstPtr cstr) radix)

mpNumBits :: MP -> IO Int
mpNumBits = mkGetSize withMP botan_mp_num_bits

mpNumBytes :: MP -> IO Int
mpNumBytes = mkGetSize withMP botan_mp_num_bytes

mpToBin :: MP -> IO ByteString
mpToBin mp = withMP mp $ \ mpPtr -> do
    numBytes <- mpNumBytes mp
    allocBytes numBytes $ \ bytesPtr -> do
        throwBotanIfNegative_ $ botan_mp_to_bin mpPtr bytesPtr

-- NOTE: Awkward, more like mpSetFromBin
--  When we wrap it in higher level, fromBin should be :: ByteString -> IO Integer
mpFromBin :: MP -> ByteString -> IO ()
mpFromBin = mkSetBytesLen withMP (\ mp cbytes len -> botan_mp_from_bin mp (ConstPtr cbytes) len)

mpToWord32 :: MP -> IO Word32
mpToWord32 mp = withMP mp $ \ mpPtr -> do
    alloca $ \ valPtr -> do
        throwBotanIfNegative_ $ botan_mp_to_uint32 mpPtr valPtr
        peek valPtr

mpIsPositive :: MP -> IO Bool
mpIsPositive = mkGetBoolCode withMP botan_mp_is_positive

mpIsNegative :: MP -> IO Bool
mpIsNegative = mkGetBoolCode withMP botan_mp_is_negative

mpFlipSign :: MP -> IO ()
mpFlipSign = mkAction withMP botan_mp_flip_sign

mpIsZero :: MP -> IO Bool
mpIsZero = mkGetBoolCode withMP botan_mp_is_zero

mpAddWord32 :: MP -> MP -> Word32 -> IO ()
mpAddWord32 result x y = withMP result $ \ resultPtr -> do
    withMP x $ \ xPtr -> do
        throwBotanIfNegative_ $ botan_mp_add_u32 resultPtr xPtr y

mpSubWord32 :: MP -> MP -> Word32 -> IO ()
mpSubWord32 result x y = withMP result $ \ resultPtr -> do
    withMP x $ \ xPtr -> do
        throwBotanIfNegative_ $ botan_mp_sub_u32 resultPtr xPtr y

mpAdd :: MP -> MP -> MP -> IO ()
mpAdd = mkBinaryOp withMP botan_mp_add

mpSub :: MP -> MP -> MP -> IO ()
mpSub = mkBinaryOp withMP botan_mp_sub

mpMul :: MP -> MP -> MP -> IO ()
mpMul = mkBinaryOp withMP botan_mp_mul

mpDiv :: MP -> MP -> MP -> MP -> IO ()
mpDiv = mkBinaryDuplexOp withMP botan_mp_div

mpModMul :: MP -> MP -> MP -> MP -> IO ()
mpModMul = mkTrinaryOp withMP botan_mp_mod_mul

-- NOTE: Cannot use mkGetBoolCode unless
mpEqual :: MP -> MP -> IO Bool
mpEqual a b = withMP a $ \ aPtr -> do
    withMP b $ \ bPtr -> do
        throwBotanCatchingBool $ botan_mp_equal aPtr bPtr

-- TODO: Convert Int to Ordering in >1:1 low-level bindings
mpCmp :: MP -> MP -> IO Int
mpCmp a b = withMP a $ \ aPtr -> do
    withMP b $ \ bPtr -> do
        alloca $ \ resultPtr -> do
            throwBotanIfNegative_ $ botan_mp_cmp resultPtr aPtr bPtr
            fromIntegral <$> peek resultPtr

mpSwap :: MP -> MP -> IO ()
mpSwap a b = withMP a $ \ aPtr -> do
    withMP b $ \ bPtr -> do
        throwBotanIfNegative_ $ botan_mp_swap aPtr bPtr

mpPowMod :: MP -> MP -> MP -> MP -> IO ()
mpPowMod = mkTrinaryOp withMP botan_mp_powmod

mpLeftShift :: MP -> MP -> Int -> IO ()
mpLeftShift = mkUnaryOp_csize withMP botan_mp_lshift

mpRightShift :: MP -> MP -> Int -> IO ()
mpRightShift = mkUnaryOp_csize withMP botan_mp_rshift

mpModInverse :: MP -> MP -> MP -> IO ()
mpModInverse = mkBinaryOp withMP botan_mp_mod_inverse

mpRandBits :: MP -> RNG -> Int -> IO ()
mpRandBits mp rng sz = withMP mp $ \ mpPtr -> do
   withRNG rng $ \ botanRNG -> do
        throwBotanIfNegative_ $ botan_mp_rand_bits mpPtr botanRNG (fromIntegral sz)

-- NOTE: Never includes upper bound
mpRandRange :: MP -> RNG -> MP -> MP -> IO ()
mpRandRange mp rng lower upper = withMP mp $ \ mpPtr -> do
   withRNG rng $ \ botanRNG -> do
        withMP lower $ \ lowerPtr -> do
            withMP upper $ \ upperPtr -> do
                throwBotanIfNegative_ $ botan_mp_rand_range mpPtr botanRNG lowerPtr upperPtr

mpGCD :: MP -> MP -> MP -> IO ()
mpGCD = mkBinaryOp withMP botan_mp_gcd

-- NOTE: Miller–Rabin primality test
mpIsPrime :: MP -> RNG -> Int -> IO Bool
mpIsPrime mp rng probability = withMP mp $ \ mpPtr -> do
    withRNG rng $ \ botanRNG -> do
        throwBotanCatchingBool $ botan_mp_is_prime mpPtr botanRNG (fromIntegral probability)

mpGetBit :: MP -> Int -> IO Bool
mpGetBit = mkGetBoolCode_csize withMP botan_mp_get_bit

mpSetBit :: MP -> Int -> IO ()
mpSetBit = mkSetCSize withMP botan_mp_set_bit

mpClearBit :: MP -> Int -> IO ()
mpClearBit = mkSetCSize withMP botan_mp_clear_bit

--
-- Helpers
--

-- int botan_...(botan_mp_t dest, const botan_mp_t source);
type UnaryOp ptr = ptr -> ptr -> IO CInt

mkUnaryOp :: WithPtr typ ptr -> UnaryOp ptr -> typ -> typ -> IO ()
mkUnaryOp withPtr unary dest source = withPtr dest $ \ destPtr -> do
    withPtr source $ \ sourcePtr -> do
        throwBotanIfNegative_ $ unary destPtr sourcePtr

-- int botan_...(botan_mp_t dest, const botan_mp_t source, size_t factor);
type UnaryOp_csize ptr = ptr -> ptr -> CSize -> IO CInt

mkUnaryOp_csize :: WithPtr typ ptr -> UnaryOp_csize ptr -> typ -> typ -> Int -> IO ()
mkUnaryOp_csize withPtr unary dest source factor  = withPtr dest $ \ destPtr -> do
    withPtr source $ \ sourcePtr -> do
        throwBotanIfNegative_ $ unary destPtr sourcePtr (fromIntegral factor)

-- int botan_...(botan_mp_t dest, const botan_mp_t a, const botan_mp_t b);
type BinaryOp ptr = ptr -> ptr -> ptr -> IO CInt

mkBinaryOp :: WithPtr typ ptr -> BinaryOp ptr -> typ -> typ -> typ -> IO ()
mkBinaryOp withPtr binary dest a b = withPtr dest $ \ destPtr -> do
    withPtr a $ \ aPtr -> do
        withPtr b $ \ bPtr -> do
            throwBotanIfNegative_ $ binary destPtr aPtr bPtr

-- int botan_...(botan_mp_t a, botan_mp_t b, const botan_mp_t x, const botan_mp_t y);
type BinaryDuplexOp ptr = ptr -> ptr -> ptr -> ptr -> IO CInt

-- NOTE: Do not confuse for mkTrinaryOp
mkBinaryDuplexOp :: WithPtr typ ptr -> BinaryDuplexOp ptr -> typ -> typ -> typ -> typ -> IO ()
mkBinaryDuplexOp withPtr binary a b x y = withPtr a $ \ aPtr -> do
    withPtr b $ \ bPtr -> do
        withPtr x $ \ xPtr -> do
            withPtr y $ \ yPtr -> do
                throwBotanIfNegative_ $ binary aPtr bPtr xPtr yPtr

-- int botan_...(botan_mp_t a, botan_mp_t b, const botan_mp_t x, const botan_mp_t y);
type TrinaryOp ptr = ptr -> ptr -> ptr -> ptr -> IO CInt

-- NOTE: Do not confuse for mkBinaryDuplexOp
mkTrinaryOp :: WithPtr typ ptr -> TrinaryOp ptr -> typ -> typ -> typ -> typ -> IO ()
mkTrinaryOp withPtr binary a x y z = withPtr a $ \ aPtr -> do
    withPtr x $ \ xPtr -> do
        withPtr y $ \ yPtr -> do
            withPtr z $ \ zPtr -> do
                throwBotanIfNegative_ $ binary aPtr xPtr yPtr zPtr