lschoe / mpyc

MPyC: Multiparty Computation in Python
MIT License
367 stars 76 forks source link

Computation parties and input parties #42

Closed gutjuri closed 1 year ago

gutjuri commented 1 year ago

Hello, is it possible to have an architecture as follows: There are "Input Parties" $I_1...I_n$ and computation parties $P_1...P_m$.

Each of the Input Parties have one secret input. They split the input into $m$ shares and provide these shares to the computation parties. Afterwards, they leave the computation. Each of the computation parties now has $n$ shares of secret inputs. They perform some computation on it and retrieve the result.

I skimmed the demos and found https://github.com/lschoe/mpyc/blob/master/demos/unanimous.py, in which the sets of Input Parties and Computation Parties differ. However, I'm unsure whether or not an architecture as I described would be possible with MPyC.

Is this possible, and if yes, what methods can be used for this?

I'd appreciate your advice! Thank you very much for providing library as Open Source, helps me a lot!

lschoe commented 1 year ago

Good question. The basic scenario is indeed that several parties send input, then all parties compute, and then several parties receive output.

A scenario with lots of input parties is not directly supported. It's possible though to change the constellation of parties during runtime, and then use a block delimited with await mpc.start() and await.mpc.shutdown() to run an MPC computation for the specified set of parties. But there's currently no high-level API support for that.

Maybe an interesting alternative is to consider the scenario of the elgamal.py demo. The parties generate a key for a threshold ElGamal cryptosystem, and they apply threshold decryption given some input ciphertexts (potentially from outside parties), like in an election: https://github.com/lschoe/mpyc/blob/8f1d9ef4d53e5bd66c12e7b704b226fff291693e/demos/elgamal.py#L79-L103 The plaintexts can then be used for further processing. It is also shown in the demo how to keep the decrypted plaintexts hidden: instead of public output, the plaintexts are then made available in secret-shared form only.

gutjuri commented 1 year ago

Thanks for your quick answer! I think I managed to achieve what I tried using the following code:

from mpyc.runtime import mpc
import mpyc.runtime as rt
from mpyc import sectypes
from mpyc import asyncoro
import mpyc.secgroups
import mpyc.random
import mpyc.statistics
import mpyc.seclists

comp_parties = list(range(3))
inp_parties = list(range(len(comp_parties), len(mpc.parties)))
secint = mpc.SecInt()

if mpc.pid in inp_parties:
    inp = mpc.pid ** 2
else:
    inp = None

# Gather all inputs
mpc.run(mpc.start())
inputs = mpc.input(secint(inp), senders=inp_parties)
mpc.run(mpc.shutdown())

# Input parties can leave now
if mpc.pid in inp_parties:
    exit(0)

# Update the runtime as to only include computation parties
newParties = list(filter(lambda p: p.pid in comp_parties, mpc.parties))
runtime = rt.Runtime(mpc.pid, newParties, mpc.options)
sectypes.runtime = runtime
asyncoro.runtime = runtime
mpyc.seclists.runtime = runtime
mpyc.secgroups.runtime = runtime
mpyc.random.runtime = runtime
mpyc.statistics.runtime = runtime
mpc = runtime

# Now perform data analysis
mpc.run(mpc.start())
res = mpc.output(mpc.prod(inputs))
mpc.run(mpc.shutdown())

print(f"Party {mpc.pid}: {res}")

This example uses 3 computation parties. However, in order to run it, I have to manually specify the threshold T=1. If I want a larger threshold, I also have to adjust the number of computation parties. This brings me to an observation: Is MPyC in its standard configuration secure if M=3? Because if M=3, then T=1, which would mean that every party can decrypt secret shares on their own(?).

Thank you for suggesting the alternative. While it's certainly interesting, I think that is not quite what I'm looking for. Again, thanks for your answer; I think I have now achieved what I attempted.

lschoe commented 1 year ago

Nice! That's an interesting way to do it.

To get rid of all the imports you can replace the block in the middle, where you update the runtime, with this:

mpc.parties = mpc.parties[:3]
mpc.threshold = 1

The meaning of say threshold $t=1$ for $m=3$ is that one corrupted party is tolerated and that two or more honest parties can recover the secrets. This approach is common in the MPC literature. In literature about secret sharing one usually refers to this as a 2-out-of-3 threshold secret sharing scheme: hence MPyC uses a $t+1$ -out-of- $m$ threshold scheme.

An issue is that the input parties get shares of each others inputs, so if two of them collude they can recover all inputs. Instead of mpc.input() you could use mpc.transfer(), which lets you specify a group of senders and a group of receivers. The input parties first generate a random split of their inputs, and then you transfer the shares using three calls to mpc.transfer() in total (one call per receiving compute party).