Power

This section demonstrates raising secret-shared values to a power. As always, this is computed element-wise on arrays of any shape.

Note that for this function, we are raising secret-shared values to a public, unencoded, non-negative integer power that is known to all players. The results are secret shared, maintaining the privacy of the inputs.

In this case, we raise a vector of values \([-1, 2, 3.4, -2.3]\) to the power 3, returning \([-1, 8, 39.304, -12.167]\)

[4]:
import logging

import numpy

from cicada.additive import AdditiveProtocolSuite
from cicada.communicator import SocketCommunicator
from cicada.logger import Logger

logging.basicConfig(level=logging.INFO)

def main(communicator):
    log = Logger(logging.getLogger(), communicator)
    protocol = AdditiveProtocolSuite(communicator)

    base = numpy.array([-1, 2, 3.4, -2.3]) if communicator.rank == 0 else None
    exponent = numpy.array(3)

    base_share = protocol.share(src=0, secret=base, shape=(4,))
    power_share = protocol.power(base_share, exponent)
    power = protocol.reveal(power_share)
    log.info(f"Player {communicator.rank} power: {power}")

SocketCommunicator.run(world_size=3, fn=main);
INFO:root:Player 0 power: [ -1.           8.          39.30377197 -12.16680908]
INFO:root:Player 1 power: [ -1.           8.          39.30377197 -12.16680908]
INFO:root:Player 2 power: [ -1.           8.          39.30377197 -12.16680908]

Note that the results 39.30381775 and -12.16680908 are slightly off due to the limited precision of our fixed point encoding.

The normal numpy rules for Broadcasting apply, so you could raise all of the base values to the same exponent as above, or pass a different exponent for every base value, or other broadcastable configurations:

[5]:
def main(communicator):
    log = Logger(logging.getLogger(), communicator)
    protocol = AdditiveProtocolSuite(communicator)

    base = numpy.array([2, 2, 2, 2, 2]) if communicator.rank == 0 else None
    exponent = numpy.array([0, 1, 2, 3, 4])

    base_share = protocol.share(src=0, secret=base, shape=(5,))
    power_share = protocol.power(base_share, exponent)
    power = protocol.reveal(power_share)
    log.info(f"Player {communicator.rank} power: {power}")

SocketCommunicator.run(world_size=3, fn=main);
INFO:root:Player 0 power: [ 1.  2.  4.  8. 16.]
INFO:root:Player 1 power: [ 1.  2.  4.  8. 16.]
INFO:root:Player 2 power: [ 1.  2.  4.  8. 16.]
[8]:
def main(communicator):
    log = Logger(logging.getLogger(), communicator)
    protocol = AdditiveProtocolSuite(communicator)

    base = numpy.array([[3, 3],[3, 3]]) if communicator.rank == 0 else None
    exponent = numpy.array([1, 2])

    base_share = protocol.share(src=0, secret=base, shape=(2, 2))
    power_share = protocol.power(base_share, exponent)
    power = protocol.reveal(power_share)
    log.info(f"Player {communicator.rank} power:\n{power}")

SocketCommunicator.run(world_size=3, fn=main);
INFO:root:Player 0 power:
[[3. 9.]
 [3. 9.]]
INFO:root:Player 1 power:
[[3. 9.]
 [3. 9.]]
INFO:root:Player 2 power:
[[3. 9.]
 [3. 9.]]