hMPC-0.1.0.0: app/Runtime.hs
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{- |
The hMPC runtime module is used to execute secure multiparty computations.
Parties perform computations on secret-shared values by exchanging messages.
Shamir's threshold secret sharing scheme is used for finite fields of any order
exceeding the number of parties. hMPC provides many secure data types, ranging
from numeric types to more advanced types, for which the corresponding operations
are made available through Haskell's mechanism for operator overloading.
-}
module Runtime (secIntGen, secFldGen, runMpc, runMpcWithArgs, runSession, Input, input, Output(..), transfer, (.+), (.-), (.*), (./), srecip, (.^), (.<), (.<=), (.>), (.==), isZero, isZeroPublic, ssignum, argmaxfunc, argmax, smaximum, ssum, sproduct,sall, randomBits, inProd, schurProd, matrixProd, IfElse(..), ifElseList, async, await) where
import Control.Lens.Traversal
import Data.Maybe
import Data.List.Split
import Data.List
import Data.Bits
import Text.Printf
import System.Info (os)
import Control.Concurrent
import Control.Monad.State
import Asyncoro
import System.Process
import System.Environment
import System.Random
import Shamir
import Prelude
import Types
import Network.Socket
import Parser
import SecTypes
import FinFields
import Data.Serialize (encode, decode, Serialize)
import qualified Data.ByteString as BS
import Data.Time
import System.Log.Logger
import System.Log.Formatter
import System.Log.Handler.Simple
import System.Log.Handler (setFormatter)
import System.IO
import Options.Applicative (Parser)
-- | Runs 'MPC' computation
runMpc :: SIO a -> IO a
runMpc = \action -> do
conf <- Runtime.setup =<< Parser.getArgParser
runSIO action conf
-- | Runs 'MPC' computation with user arguments
runMpcWithArgs :: Parser b -> (b -> SIO a) -> IO a
runMpcWithArgs parser = \action -> do
(mpcOpts, userOpts) <- Parser.getArgParserExtra parser
conf <- Runtime.setup mpcOpts
runSIO (action userOpts) conf
-- | Start and Stop hMPC runtime
runSession :: SIO a -> SIO a
runSession action = do
env <- Runtime.start
liftIO $ runSIO (do val <- action; Runtime.shutdown; return val) env
exchangeShares :: [BS.ByteString] -> SIO [MVar BS.ByteString]
exchangeShares inShares = do
parties <- gets parties
forM_ (zip parties inShares) $ \(party, bytes) ->
when (isJust $ sock party) (sendMessage bytes party)
forM (zip parties [0..]) $ \(party, index) ->
case sock party of
Just _ -> receiveMessage party
Nothing -> liftIO $ newMVar (inShares !! index)
-- | Transfer serializable Haskell objects
transfer :: (Serialize a) => a -> SIO (MVar [a])
transfer val = do
parties <- gets parties
let encVal = (encode val)
out <- async $ do
forM_ parties $ \party ->
when (isJust $ sock party) (sendMessage encVal party)
forM parties $ \party ->
case sock party of
Just _ -> do
bytes <- await =<< receiveMessage party
case (decode bytes) of
Right msg -> return msg
Nothing -> return val
return out
-- | Input x to the computation.
--
-- Value x is a secure object, or a list of secure objects.
class Input a b | a -> b where
input :: a -> SIO b
instance Input (SIO SecureTypes) [SIO SecureTypes] where
input a = map head <$> input [a]
instance Input [SIO SecureTypes] [[SIO SecureTypes]] where
input xm = do
x <- sequence xm
Env{parties=parties, options=opts, gen=_gen} <- get
outslist <- asyncListList (fromInteger (m opts)) (length x) $ do
xf <- gather x
let ftype = (head xf)
length = (byteLength . meta) ftype
(inShares, g'') = Shamir.randomSplit ftype xf (threshold opts) (m opts) _gen
modify (\env -> env{gen = g''})
forM_ (zip parties inShares) $ \(party, val) ->
when (isJust $ sock party) (sendMessage (toBytes length val) party)
forM (zip parties [0..]) $ \(party, index) -> do
shares <- case sock party of
Just _ -> fromBytes length <$> (await =<< receiveMessage party)
Nothing -> return (inShares !! index)
return $ map (\share -> ftype{value = share}) shares
return $ (map . map) (\out -> return (head x){share = out}) outslist
class Reshare a b | a -> b where
reshare :: a -> SIO b
instance Reshare FiniteField FiniteField where
reshare a = head <$> reshare [a]
instance Reshare [FiniteField] [FiniteField] where
reshare x = do
Env{parties=parties, options=opts, gen=_gen} <- incPC >> get
let ftype = head x
length = (byteLength . meta) ftype
(s, g'') = Shamir.randomSplit ftype x (threshold opts) (m opts) _gen
inShares = map (toBytes length) s
modify (\env -> env{gen = g''})
shares <- exchangeShares inShares
points <- forM (zip shares parties) $ \(share, party) -> do
val <- fromBytes length <$> (await share)
return ((pid party) + 1, val)
return (Shamir.recombine ftype points)
-- | Output the value of x to the receivers specified.
-- Value x is a secure object, or a list of secure objects.
--
-- A secure integer is output as a Haskell Integer
class Output a b | a -> b where
output :: a -> SIO (MVar b)
instance Output (SIO SecureTypes) Integer where
output a = _output [a] head
instance Output [SIO SecureTypes] [Integer] where
output a = _output a id
_output :: [SIO SecureTypes] -> ([Integer] -> b) -> SIO (MVar b)
_output xm convert = do
x <- sequence xm
parties <- gets parties
out <- async $ do
s <- gather x
let inShares = map value s
length = (byteLength . meta . head) s
let inSharesEncoded = toBytes length inShares
forM_ parties $ \party ->
when (isJust $ sock party) (sendMessage inSharesEncoded party)
points <- forM parties $ \party -> do
shares <- case sock party of
Just _ -> fromBytes length <$> (await =<< receiveMessage party)
Nothing -> return inShares
return ((pid party) + 1, shares)
let y = map value (recombine ((field . head) x) points)
return $ convert y
return out
shutdown :: SIO ()
shutdown = do
Env{parties=parties, options=opt, forkIOBarrier=barrier, startTime=_startTime} <- get
-- wait until all forkIO tasks have completed
liftIO $ Asyncoro.decreaseBarrier barrier
liftIO $ takeMVar (signal barrier)
endTime <- liftIO $ getCurrentTime
bytes <- mapM (await . nbytesSent) parties
let elapsedTime = diffUTCTime endTime _startTime
liftIO $ logging INFO $ printf "Computation time: %s sec | bytes sent: %d \n" (show elapsedTime) (sum bytes)
-- Synchronize with all parties before shutdown
await =<< transfer (myPid opt)
-- close connections peer_pid > pid
liftIO $ forM_ (filter (\x -> ((pid x) > (myPid opt))) parties) $ \party -> do
case (sock party) of
Just _sock -> close _sock
-- | Secure addition of a and b.
(.+) :: SIO SecureTypes -> SIO SecureTypes -> SIO SecureTypes
(.+) am bm = do
(a, b) <- sequenceOf both (am, bm)
out <- async $ do
(af, bf) <- gather (a, b)
return (af + bf)
return (_coerce a b){share = out}
-- | Secure subtraction of a and b.
(.-) :: SIO SecureTypes -> SIO SecureTypes -> SIO SecureTypes
(.-) am bm = do
(a, b) <- sequenceOf both (am, bm)
out <- async $ do
(af, bf) <- gather (a, b)
return (af - bf)
return (_coerce a b){share = out}
-- | Secure multiplication of a and b.
(.*) :: SIO SecureTypes -> SIO SecureTypes -> SIO SecureTypes
(.*) am bm = do
(a, b) <- sequenceOf both (am, bm)
out <- async $ do
(af, bf) <- gather (a, b)
reshare (af * bf)
return (_coerce a b){share = out}
-- | Secure division of a by b, for nonzero b.
(./) :: SIO SecureTypes -> SIO SecureTypes -> SIO SecureTypes
(./) am bm = do
(recip bm) * am
-- | Secure reciprocal (multiplicative field inverse) of a, for nonzero a.
srecip :: SIO SecureTypes -> SIO SecureTypes
srecip am = do
a <- am
out <- async $ fix $ \loop -> do
[r] <- _randoms a 1 Nothing
ar <- await =<< output ((return a) .* (return r))
if (ar == 0)
then loop
else do
rfld <- gather r
return (rfld / rfld{value = ar})
return a{share = out}
-- | Secure exponentiation a raised to the power of b, for public integer b.
(.^) :: SIO SecureTypes -> Integer -> SIO SecureTypes
(.^) am b = do
a <- am
sproduct $ replicate (fromIntegral b) (return a)
-- | Secure comparison a < b.
(.<) :: SIO SecureTypes -> SIO SecureTypes -> SIO SecureTypes
(.<) am bm = ssignum True False (am - bm)
-- | Secure comparison a <= b.
(.<=) :: SIO SecureTypes -> SIO SecureTypes -> SIO SecureTypes
(.<=) am bm = 1 - (bm .< am)
-- | Secure comparison a > b.
(.>) :: SIO SecureTypes -> SIO SecureTypes -> SIO SecureTypes
(.>) am bm = (bm .< am)
-- | Secure comparison a == b.
(.==) :: SIO SecureTypes -> SIO SecureTypes -> SIO SecureTypes
(.==) am bm = isZero (am - bm)
-- | Secure zero test a == 0.
isZero :: SIO SecureTypes -> SIO SecureTypes
isZero am = do
a <- am
case a of
SecFld {field=fld} -> (1 - ((return a) .^ ((modulus . meta) fld - 1))) -- todo modulus = order
_ -> ssignum False True (return a)
-- | Secure public zero test of a.
isZeroPublic :: SIO SecureTypes -> SIO (MVar Bool)
isZeroPublic am = do
a <- am
[r] <- _randoms a 1 Nothing
out <- async $ do
(afld, rfld) <- gather (a, r)
res <- await =<< (output $ setShare a (value (afld * rfld)))
return (res == 0)
return out
-- | Secure sign(um) of a, return -1 if a < 0 else 0 if a == 0 else 1.
--
-- If Boolean flag LT is set, perform a secure less than zero test instead, and
-- return 1 if a < 0 else 0, saving the work for a secure equality test.
-- If Boolean flag EQ is set, perform a secure equal to zero test instead, and
-- return 1 if a == 0 else 0, saving the work for a secure comparison.
ssignum :: Bool -> Bool -> SIO SecureTypes -> SIO SecureTypes
ssignum True True _ = error "lt and eq both true"
ssignum lt eq am = do
a <- am
opt <- gets options
let l = (bitLength a)
r_bits <- sequence =<< randomBits (return a) l False
[r] <- _randoms a 1 $ Just $ 1 `shiftL` (secParam opt)
out <- async $ do
r_bits_fld <- gather r_bits
let r_modl = foldr (\x acc -> (acc `shiftL` 1) + (value x)) 0 r_bits_fld
(r_divl, af) <- gather (r, a)
let a_rmodl = af + fromInteger ((1 `shiftL` l) + r_modl)
c <- (`mod` (1 `shiftL` l))
<$> (await =<< output (setShare a (value $ a_rmodl + fromInteger ((value r_divl) `shiftL` l))))
z1 <- if not eq then do
s_sign <- value <$> (randomBits (return a) 1 True >>= head >>= gather)
let (e, sumXors) = foldl (\(e, sumXors) (bit, i) ->
let c_i = ((c `shiftR` i) .&. 1)
in (setShare a (s_sign + (value bit) - c_i + 3 * sumXors) : e,
sumXors + if c_i == 1 then 1 - (value bit) else (value bit)))
([], 0) (zip (reverse r_bits_fld) [l-1, l-2..])
g <- await =<< (isZeroPublic $ sproduct (setShare a (s_sign - 1 + 3*sumXors) : e))
let h = if g then 3 - s_sign else 3 + s_sign
return $ (fromInteger (c + (h `shiftL` (l - 1))) - a_rmodl) / fromInteger (1 `shiftL` l)
else return FiniteField{}
if not lt then do
h <- await . share =<< sall (map (\(bit, i) ->
setShare a $ value $ if ((c `shiftR` i) .&. 1) == 1 then bit else 1 - bit) (zip r_bits_fld [0..]))
if eq then return h
else reshare ((h - 1) * (2*z1 - 1))
else return z1
return a{share = out}
argmaxfunc :: [[SIO SecureTypes]] -> ([SIO SecureTypes] -> [SIO SecureTypes] -> SIO SecureTypes) -> SIO (SIO SecureTypes, [SIO SecureTypes])
argmaxfunc [xm] _ = do
x <- sequence xm
return (setShare (head x) 0, map return x)
argmaxfunc x f = do
let n = length x
let (x0, x1) = splitAt (n `div` 2) x
(i0, m0) <- argmaxfunc x0 f
(i1, m1) <- argmaxfunc x1 f
c <- return <$> f m0 m1
a <- ifElse c (i1 + fromIntegral (n `div` 2)) i0
m <- ifElse c m1 m0
return (return a, m)
-- | Secure argmax of all given elements in x.
--
-- In case of multiple occurrences of the maximum values,
-- the index of the first occurrence is returned.
argmax :: [SIO SecureTypes] -> SIO (SIO SecureTypes, SIO SecureTypes)
argmax [xm] = do
x <- xm
return (setShare x 0, return x)
argmax x = do
let n = length x
let (x0, x1) = splitAt (n `div` 2) x
(i0, m0) <- argmax x0
(i1, m1) <- argmax x1
c <- return <$> (m0) .< (m1)
a <- ifElse c (i1 + fromIntegral (n `div` 2)) i0
m <- ifElse c m1 m0
return (return a, return m)
-- | Secure maximum of all given elements in x, similar to Haskell's built-in maximum.
smaximum :: [SIO SecureTypes] -> SIO SecureTypes
smaximum [a] = a
smaximum x = do
let (x0, x1) = splitAt (length x `div` 2) x
m0 <- return <$> smaximum x0
m1 <- return <$> smaximum x1
(m0 .< m1) * (m1 - m0) + m0
-- | Secure sum of all elements in x, similar to Haskell's built-in sum.
ssum :: [SIO SecureTypes] -> SIO SecureTypes
ssum xm = do
x <- sequence xm
out <- async $ sum <$> gather x
return (head x){share = out}
-- | Secure product of all elements in x, similar to Haskell's product.
--
-- Runs in log_2 len(x) rounds).
sproduct :: [SIO SecureTypes] -> SIO SecureTypes
sproduct xm = do
x <- sequence xm
out <- async $ head <$> iterate (\xold -> do
(xmul, leftover) <- pairwise (*) <$> xold
(leftover ++) <$> reshare xmul) (gather x) !! ((ceiling . logBase 2 . fromIntegral) (length x))
return (head x){share = out}
-- | Secure all of elements in x, similar to Haskell's built-in all.
--
-- Elements of x are assumed to be either 0 or 1 (Boolean).
-- Runs in log_2 len(x) rounds).
sall :: [SIO SecureTypes] -> SIO SecureTypes
sall xm = do
x <- sequence xm
out <- async $ head <$> iterate (\xold -> do
(xmul, leftover) <- pairwise (*) <$> xold
(leftover ++) <$> reshare xmul) (gather x) !! ((ceiling . logBase 2 . fromIntegral) (length x))
return (head x){share = out}
-- | Return n secure random values of the given type in the given range.
_randoms :: SecureTypes -> Integer -> Maybe Integer -> SIO [SecureTypes]
_randoms st n bound = do
t <- threshold <$> (gets options)
(g', g'') <- System.Random.split <$> gets gen
let fld = field st
_bound = case bound of
Just b -> 1 `shiftL` max 0 ((floor . logBase 2 . fromIntegral) (b `div` (t + 1)))
Nothing -> (modulus . meta) fld -- todo modulus = order
x = take (fromIntegral n) $ randomRs (0, _bound - 1) g'
modify (\env -> env{gen = g''})
xlist <- input $ map (\rand -> do
randmvar <- liftIO $ newMVar fld{value = rand} :: SIO (MVar FiniteField)
return st{share = randmvar}) x
forM (transpose xlist) $ \_x -> ssum _x
-- | Return n secure uniformly random bits of the given type.
randomBits :: SIO SecureTypes -> Int -> Bool -> SIO [SIO SecureTypes]
randomBits stm n signed = do
st <- stm
(g', g'') <- System.Random.split <$> gets gen
let x = take (fromIntegral n) $ randomRs (0, 1) g'
modify (\env -> env{gen = g''})
xlist <- input $ map (\bit -> do
randmvar <- liftIO $ newMVar (field st){value = ((2*bit)-1)} :: SIO (MVar FiniteField)
return st{share = randmvar}) x
secbits <- forM (transpose xlist) $ \x -> do
let secbit = sproduct x
if signed then secbit
else (secbit + 1) * fromInteger (((modulus . meta . field) st + 1) `shiftR` 1) -- todo modulus = characteristics
return $ map (\secbit -> return secbit) secbits
-- | Secure dot product of x and y (one resharing).
inProd :: [SIO SecureTypes] -> [SIO SecureTypes] -> SIO SecureTypes
inProd xm ym = do
(x, y) <- sequenceOf (both . traversed) (xm, ym)
out <- async $ do
(xf, yf) <- gather (x, y)
reshare $ sum $ zipWith (*) xf yf
return (head x){share = out}
-- | Secure entrywise multiplication of vectors x and y.
schurProd :: [SIO SecureTypes] -> [SIO SecureTypes] -> SIO [SIO SecureTypes]
schurProd xm ym = do
(x, y) <- sequenceOf (both . traversed) (xm, ym)
outs <- asyncList (length x) $ do
(xf, yf) <- gather (x, y)
reshare $ zipWith (*) xf yf
return $ map (\out -> return (head x){share = out}) outs
-- | Secure matrix product of A with (transposed) B.
matrixProd :: [[SIO SecureTypes]] -> [[SIO SecureTypes]] -> Bool -> SIO [[SIO SecureTypes]]
matrixProd am bm tr = do
(a, b) <- sequenceOf (both . traversed . traversed) (am, bm)
let n2 = if tr then (length b) else (length (head b))
outslist <- asyncListList (length a) n2 $ do
(af, bf) <- gather (a, b)
let bft = if tr then bf else transpose bf
chunksOf n2 <$> reshare (concat [[sum $ zipWith (*) ai bi | bi <- bft] | ai <- af])
return $ (map . map) (\out -> return ((head . head) a){share = out}) outslist
-- | Secure selection between x and y based on condition c.
class IfElse a b | a -> b where
ifElse :: SIO SecureTypes -> a -> a -> SIO b
instance IfElse (SIO SecureTypes) (SecureTypes) where
ifElse cm xm ym = do
y <- return <$> ym
cm * (xm - y) + y
instance IfElse [SIO SecureTypes] [SIO SecureTypes] where
ifElse = ifElseList
ifElseList :: SIO SecureTypes -> [SIO SecureTypes] -> [SIO SecureTypes] -> SIO [SIO SecureTypes]
ifElseList am xm ym = do
(x, y) <- sequenceOf (both . traversed) (xm, ym)
a <- am
outs <- asyncList (length x) $ do
(af, xf, yf) <- gather (a, x, y)
reshare $ map (\(x_i, y_i) -> x_i{value = (value af) * ((value x_i) - (value y_i)) + (value y_i)}) (zip xf yf)
return $ map (\out -> return (head x){share = out}) outs
randomR' :: (Integer, Integer) -> SIO Integer
randomR' range = do
_gen <- gets gen
let (value, newGen) = randomR range _gen
modify (\env -> env{gen = newGen})
return value
pairwise :: (a -> a -> a) -> [a] -> ([a], [a])
pairwise _ [] = ([],[])
pairwise _ [x] = ([],[x])
pairwise f (x:y:xs) =
let (z, leftover) = pairwise f xs
in (f x y : z, leftover)
receiveMessage :: Party -> SIO (MVar BS.ByteString)
receiveMessage party = do
pc <- gets pc
Asyncoro.receive pc party
-- | Send data to given peer, labeled by current program counter.
sendMessage :: BS.ByteString -> Party -> SIO ()
sendMessage bytes party = do
pc <- gets pc
Asyncoro.send pc bytes party
-- Start the hMPC runtime.
start :: SIO Env
start = do
Env{parties=parties, options=opt} <- get
liftIO $ do
parties <- Asyncoro.createConnections (fromInteger $ myPid opt) parties
countVar <- newMVar 1
gen <- newStdGen
signalVar <- newEmptyMVar
startTime <- getCurrentTime
return $ Env parties 0 opt (Barrier countVar signalVar) gen startTime
setup :: Options -> IO Env
setup opt = do
setNumCapabilities =<< maybe getNumCapabilities return (nrThreads opt)
h <- streamHandler stdout INFO >>= \lh -> return $
setFormatter lh (tfLogFormatter "%Y-%m-%d %H:%M:%S%Q" "$time $msg")
updateGlobalLogger rootLoggerName (setHandlers [h] . setLevel INFO)
Env{options = _opt, parties=_parties} <- if null (parsParties opt)
then do
let _m = if (m opt < 0) then 1 else (m opt)
when (_m > 1 && (myPid opt) == -1) $ do
exPath <- getExecutablePath
args <- getArgs
forM_ [1..(_m-1)] $ \i -> do
let cmdLine = printf "%s %s -I %d" exPath (unwords args) i
createProcess (shell $
if os == "mingw32" then ("start " ++ cmdLine)
else if os == "darwin" then printf "osascript -e 'tell application \"Terminal\" to do script \"%s\"'" cmdLine
else "")
let _mypid = if (myPid opt) >= 0 then (myPid opt) else 0
_parties = map (\i -> mkParty i "127.0.0.1" ((basePort opt) + i)) [0.._m-1]
_noASync = _m == 1 && ((noAsync opt) || not ((m opt) < 0))
return Env{options = opt{myPid = _mypid, m = _m, noAsync = _noASync}, parties = _parties}
else do
let addresses = map (\addr -> splitOn ":" addr) (parsParties opt)
_mypid = maybe (myPid opt) fromIntegral (elemIndex "" (map head addresses))
_parties = map (\(i, [host, port]) -> mkParty i (if null host then "127.0.0.1" else host) (read port)) (zip [0..] addresses)
_m = (fromIntegral . length) _parties
return Env{options = opt{myPid = _mypid, m = _m}, parties = _parties}
let threshold = ((m _opt)-1) `div` 2
return Env{options = _opt{threshold = threshold}, parties = _parties}
where
mkParty pid host port = Party{pid = pid, host = host, port = port}
-- secure types operator overloading
instance Num (SIO SecureTypes) where
(*) = (.*)
(+) = (.+)
(-) = (.-)
signum = ssignum False False
fromInteger i =
SecTypes.Literal <$> (liftIO $ newMVar $ FinFields.Literal i)
instance Fractional (SIO SecureTypes) where
(/) = (./)
recip = srecip
_coerce :: SecureTypes -> SecureTypes -> SecureTypes
_coerce (SecTypes.Literal _) f2 = f2
_coerce f1 _ = f1