Source code for cicada.przs

# Copyright 2021 National Technology & Engineering Solutions
# of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS,
# the U.S. Government retains certain rights in this software.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Pseudorandom Zero-Sharing functionality."""

import inspect

import numpy

from cicada.arithmetic import Field
from cicada.communicator.interface import Communicator, Tag


[docs] class PRZSProtocol(object): """Implements the Pseudorandom Zero-Sharing protocol of Cramer, Damgard, and Ishai. Note ---- Creating the protocol is a collective operation that *must* be implemented by all players that are members of `communicator`. Parameters ---------- communicator: :class:`cicada.communicator.interface.Communicator`, required The communicator that this protocol will use for communication. field: :class:`cicada.arithmetic.Field`, required The field from which generated values will be drawn. seed: :class:`int`, optional Seed used to initialize random number generators. For privacy, this value should be different for each player. """ def __init__(self, *, communicator, field, seed): if not isinstance(communicator, Communicator): raise ValueError("A Cicada communicator is required.") # pragma: no cover self._communicator = communicator if not isinstance(field, Field): raise ValueError("A Cicada field is required.") # pragma: no cover self._field = field # Send random seed to next party, receive random seed from prev party if communicator.world_size >= 2: # Otherwise sending seeds will segfault. next_rank = (communicator.rank + 1) % communicator.world_size prev_rank = (communicator.rank - 1) % communicator.world_size request = communicator.isend(value=seed, dst=next_rank, tag=Tag.PRZS) result = communicator.irecv(src=prev_rank, tag=Tag.PRZS) request.wait() result.wait() prev_seed = result.value else: prev_seed = seed # Setup random number generators self._g0 = numpy.random.default_rng(seed=seed) self._g1 = numpy.random.default_rng(seed=prev_seed)
[docs] def __call__(self, *, shape): """Generate an array of field values that sum to zero, using pseudorandom zero-sharing. Note ---- This is a collective operation that *must* be called by all players that are members of :attr:`communicator`. Parameters ---------- shape: :class:`tuple`, required The shape of the result. Note that all players must specify the same shape. Returns ------- przs: :class:`numpy.ndarray` The local share of zeros. """ if not isinstance(shape, tuple): shape = (shape,) przs = self.field.uniform(size=shape, generator=self._g0) self.field.inplace_subtract(przs, self.field.uniform(size=shape, generator=self._g1)) return przs
@property def communicator(self): """The :class:`~cicada.communicator.interface.Communicator` used by this protocol.""" return self._communicator # pragma: no cover @property def field(self): """The :class:`~cicada.arithmetic.Field` used by this protocol.""" return self._field