lschoe / mpyc

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

Inconsistent result between M=1 and M=3 #43

Closed MarcT0K closed 1 year ago

MarcT0K commented 1 year ago

Hi, Sorry to bother you but I bumped into a behaviour I cannot explain. To put it simply, I am doing some secure linear algebra experiments. I start from a random numpy matrix and transform it into a list of secure numbers to compute some stuff.

Initially, I tested everything in the 1-party mode. All my results were correct.

Today, I changed the number of parties to M=3. All my results are now false. When M=1, my secure list have coherent values but when M=3, the values are all incorrect. I suppose that I have done a mistake in the transformation from numpy object to secure list but I am not sure were. I struggle to pin point where is my mistake. The data transformation looks like this:

for i in range(mat.shape[0]): # for a vector
    sec_list.append(sectype(mat[i,0]))
# It is not really a sample of my codebase but it summarizes the logic

If ever you have a little nudge for me, I would be glad to hear it!

PS: in addition to the correct result, one of my coroutine even crashes with the following traceback. Something seems definitely wrong with my share generation...

Traceback (most recent call last):
  File "/usr/lib/python3.10/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/home/mdamie/.local/lib/python3.10/site-packages/mpyc/asyncoro.py", line 429, in <lambda>
    task.add_done_callback(lambda t: _reconcile(decl, t))
  File "/home/mdamie/.local/lib/python3.10/site-packages/mpyc/asyncoro.py", line 353, in _reconcile
    givn = task.result()
  File "/home/mdamie/.local/lib/python3.10/site-packages/mpyc/asyncoro.py", line 281, in _wrap_in_coro
    return await awaitable
  File "/home/mdamie/.local/lib/python3.10/site-packages/mpyc/asyncoro.py", line 269, in __await__
    val = self.coro.send(None)
  File "/home/mdamie/.local/lib/python3.10/site-packages/mpyc/runtime.py", line 572, in _reshare
    in_shares = thresha.random_split(field, x, t, m)
  File "/home/mdamie/.local/lib/python3.10/site-packages/mpyc/thresha.py", line 32, in random_split
    T_is_field = isinstance(s[0], field)  # all elts assumed of same type
IndexError: index 0 is out of bounds for axis 0 with size 0
Traceback (enclosing MPyC coroutine call):
  File "/home/mdamie/.local/lib/python3.10/site-packages/mpyc/runtime.py", line 956, in np_multiply
    return c
Traceback (most recent call last):
  File "/home/mdamie/research/secure-sparse-computations/experiments.py", line 557, in <module>
    mpc.run(main())
  File "/home/mdamie/.local/lib/python3.10/site-packages/mpyc/runtime.py", line 184, in run
    return self._loop.run_until_complete(f)
  File "/usr/lib/python3.10/asyncio/base_events.py", line 644, in run_until_complete
    raise RuntimeError('Event loop stopped before Future completed.')
RuntimeError: Event loop stopped before Future completed.
MarcT0K commented 1 year ago

By the way, the problem is present for any M above 1

lschoe commented 1 year ago

The problem is probably because you let each party generate its own random numpy matrix. In the 1-party mode this is no problem, but in the $m$-party mode with $m>1$ the parties will start from inconsistent states.

The MPyC demos show several approaches to work with randomly generated input in the multiparty setting. Basically, there are two ways.

One way is to let the parties jointly generate a random value. A first example is to jointly generate the seed for a one-way hash chain:

https://github.com/lschoe/mpyc/blob/452755559cd5ec123d482e4e8ed4935c8f982127/demos/np_onewayhashchains.py#L149

Another example is the distributed key generation (DKG) protocol for the threshold ElGamal cryptogsystem:

https://github.com/lschoe/mpyc/blob/452755559cd5ec123d482e4e8ed4935c8f982127/demos/elgamal.py#L41-L45

This way no party knows the random value, it is like a shared secret.

Another way is to let one party (usually party with mpc.pid == 0) first generate a random value locally. If it is OK that this value is public, for all parties to see, we can use mpc.transfer() to broadcast the value to all parties. Here is an example close to your case:

https://github.com/lschoe/mpyc/blob/452755559cd5ec123d482e4e8ed4935c8f982127/demos/pseudoinverse.py#L98-L113

And here's a more advanced example, where party 0 first broadcasts a random seed, and then all parties locally generate the same synthesized data:

https://github.com/lschoe/mpyc/blob/452755559cd5ec123d482e4e8ed4935c8f982127/demos/ridgeregression.py#L66-L86

I hope this solves your problem?

MarcT0K commented 1 year ago

Hi, Thank you very much for this pointer! Indeed, it solved my problem. I know better understand how the scripts are executed by the various parties and what is the interest of mpc.transfer().