def prrs_generate_random_share( self, shape: Union[tuple, torch.Size], ) -> Any: """Generates a random share using the generators held by a party. Args: shape (Union[tuple, torch.Size]): Shape for the share. Returns: Any: ShareTensor or ReplicatedSharedTensor """ from sympc.tensor import ReplicatedSharedTensor from sympc.tensor import ShareTensor share1, share2 = self._generate_random_share(shape) if self.protocol.share_class == ShareTensor: # It has encoder_precision = 0 such that the value would not be encoded share = ShareTensor( data=share1, session_uuid=self.uuid, config=Config(encoder_precision=0), ) else: share = ReplicatedSharedTensor( shares=[share1, share2], session_uuid=self.uuid, config=Config(encoder_precision=0), ) return share
def test_generate_shares() -> None: precision = 12 base = 4 x_secret = torch.Tensor([5.0]) # test with default values x_share = ShareTensor(data=x_secret) shares_from_share_tensor = MPCTensor.generate_shares(x_share, nr_parties=2) shares_from_secret = MPCTensor.generate_shares(x_secret, nr_parties=2, config=Config()) assert sum(shares_from_share_tensor).tensor == sum( shares_from_secret).tensor x_share = ShareTensor(data=x_secret, config=Config(encoder_precision=precision, encoder_base=base)) shares_from_share_tensor = MPCTensor.generate_shares(x_share, 2) shares_from_secret = MPCTensor.generate_shares( x_secret, 2, config=Config(encoder_precision=precision, encoder_base=base)) assert sum(shares_from_share_tensor).tensor == sum( shares_from_secret).tensor
def test_different_config() -> None: x = torch.tensor([1]) shares = [x, x] session_id = uuid4() config1 = Config(encoder_precision=10, encoder_base=2) config2 = Config(encoder_precision=12, encoder_base=10) x_share = ReplicatedSharedTensor(shares=shares, session_uuid=session_id, config=config1) y_share = ReplicatedSharedTensor(shares=shares, session_uuid=session_id, config=config2) # Different fixed point config assert x_share != y_share
def protobuf_session_deserializer(proto: MPCSession_PB) -> Session: id_session = UUID(bytes=proto.uuid) rank = proto.rank conf_dict = Dict._proto2object(proto=proto.config) _conf_dict = {key: value for key, value in conf_dict.items()} conf = Config(**_conf_dict) ring_size = int.from_bytes(proto.ring_size, "big") nr_parties = int.from_bytes(proto.nr_parties, "big") protocol_deserialized = proto.protocol.decode() session = Session( config=conf, uuid=id_session, ring_size=ring_size, protocol=protocol_deserialized, ) session.rank = rank session.crypto_store = CryptoStore() session.nr_parties = nr_parties if "session" in globals(): warning("Overwritting session for MPC") globals()["session"] = session return session
def __init__( self, shares: Optional[List[Union[float, int, torch.Tensor]]] = None, config: Config = Config(encoder_base=2, encoder_precision=16), session_uuid: Optional[UUID] = None, ring_size: int = 2**64, ): """Initialize ReplicatedSharedTensor. Args: shares (Optional[List[Union[float, int, torch.Tensor]]]): Shares list from which RSTensor is created. config (Config): The configuration where we keep the encoder precision and base. session_uuid (Optional[UUID]): Used to keep track of a share that is associated with a remote session ring_size (int): field used for the operations applied on the shares Defaults to 2**64 """ self.session_uuid = session_uuid self.ring_size = ring_size self.config = config self.fp_encoder = FixedPointEncoder(base=config.encoder_base, precision=config.encoder_precision) tensor_type = get_type_from_ring(ring_size) self.shares = [] if shares is not None: for i in range(len(shares)): self.shares.append(self._encode(shares[i]).to(tensor_type))
def __init__( self, data: Optional[Union[float, int, torch.Tensor]] = None, config: Config = Config(encoder_base=2, encoder_precision=16), session_uuid: Optional[UUID] = None, ring_size: int = 2**64, ) -> None: """Initialize ShareTensor. Args: data (Optional[Any]): The share a party holds. Defaults to None config (Config): The configuration where we keep the encoder precision and base. session_uuid (Optional[UUID]): Used to keep track of a share that is associated with a remote session ring_size (int): field used for the operations applied on the shares Defaults to 2**64 """ self.session_uuid = session_uuid self.ring_size = ring_size self.config = config self.fp_encoder = FixedPointEncoder(base=config.encoder_base, precision=config.encoder_precision) self.tensor: Optional[torch.Tensor] = None if data is not None: tensor_type = get_type_from_ring(ring_size) self.tensor = self._encode(data).to(tensor_type)
def bit_extraction(self, pos: int = 0) -> "ReplicatedSharedTensor": """Extracts the bit at the specified position. Args: pos (int): position to extract bit. Returns: ReplicatedSharedTensor : extracted bits at specific position. Raises: ValueError: If invalid position is provided. """ ring_bits = get_nr_bits(self.ring_size) if pos < 0 or pos > ring_bits - 1: raise ValueError( f"Invalid position for bit_extraction: {pos}, must be in range:[0,{ring_bits-1}]" ) shares = [] # logical shift bit_mask = torch.ones(self.shares[0].shape, dtype=self.shares[0].dtype) << pos shares = [share & bit_mask for share in self.shares] rst = ReplicatedSharedTensor( shares=shares, session_uuid=self.session_uuid, config=Config(encoder_base=1, encoder_precision=0), ring_size=2, ) return rst
def test_session_ring_xor(get_clients, security, bit) -> None: parties = get_clients(3) protocol = Falcon(security) session = Session(protocol=protocol, parties=parties) SessionManager.setup_mpc(session) ring_size = session.ring_size tensor_type = session.tensor_type config = Config(encoder_base=1, encoder_precision=0) x_sh1 = torch.tensor([[927021, 3701]], dtype=tensor_type) x_sh2 = torch.tensor([[805274, 401]], dtype=tensor_type) x_sh3 = torch.tensor([[-1732294, -4102]], dtype=tensor_type) bit_sh_1, bit_sh_2, bit_sh_3 = bit b_sh1 = torch.tensor([bit_sh_1], dtype=tensor_type) b_sh2 = torch.tensor([bit_sh_2], dtype=tensor_type) b_sh3 = torch.tensor([bit_sh_3], dtype=tensor_type) shares_x = [x_sh1, x_sh2, x_sh3] shares_b = [b_sh1, b_sh2, b_sh3] rst_list_x = ReplicatedSharedTensor.distribute_shares(shares=shares_x, session=session, ring_size=ring_size, config=config) rst_list_b = ReplicatedSharedTensor.distribute_shares(shares=shares_b, session=session, ring_size=ring_size, config=config) x = MPCTensor(shares=rst_list_x, session=session, shape=x_sh1.shape) b = MPCTensor(shares=rst_list_b, session=session, shape=b_sh1.shape) secret_x = ReplicatedSharedTensor.shares_sum(shares_x, ring_size) secret_b = ReplicatedSharedTensor.shares_sum(shares_b, ring_size) result = operator.xor(x, b) expected_res = secret_x ^ secret_b assert (result.reconstruct(decode=False) == expected_res).all()
def test_ineq_share_share_local(op_str, precision, base) -> None: op = getattr(operator, op_str) x = torch.Tensor([[0.125, -1.25], [-4.25, 4]]) y = torch.Tensor([[4.5, -2.5], [5, 2.25]]) x_share = ShareTensor(data=x, config=Config(encoder_base=base, encoder_precision=precision)) y_share = ShareTensor(data=y, config=Config(encoder_base=base, encoder_precision=precision)) expected_res = op(x, y) res = op(x_share, y_share) assert (res == expected_res).all()
def test_ops_share_share_local(op_str, precision, base) -> None: op = getattr(operator, op_str) x = torch.Tensor([[0.125, -1.25], [-4.25, 4]]) y = torch.Tensor([[4.5, -2.5], [5, 2.25]]) x_share = ShareTensor(data=x, config=Config(encoder_base=base, encoder_precision=precision)) y_share = ShareTensor(data=y, config=Config(encoder_base=base, encoder_precision=precision)) expected_res = op(x, y) res = op(x_share, y_share) tensor_decoded = res.fp_encoder.decode(res.tensor) assert np.allclose(tensor_decoded, expected_res, rtol=base**-precision)
def test_send_get(get_clients, precision, base) -> None: x = torch.Tensor([0.122, 1.342, 4.67]) x_share = ShareTensor(data=x, config=Config(encoder_precision=precision, encoder_base=base)) client = get_clients(1)[0] x_ptr = x_share.send(client) assert x_share == x_ptr.get()
def test_reconstruct(get_clients) -> None: clients = get_clients(2) session = Session(parties=clients) SessionManager.setup_mpc(session) a_rand = 3 a = ShareTensor(data=a_rand, config=Config(encoder_precision=0)) MPCTensor.generate_shares(secret=a, nr_parties=2, tensor_type=torch.long) MPCTensor.generate_shares( secret=a_rand, nr_parties=2, config=Config(), tensor_type=torch.long ) x_secret = torch.Tensor([1, -2, 3.0907, -4.870]) x = MPCTensor(secret=x_secret, session=session) x = x.reconstruct() assert np.allclose(x_secret, x)
def test_generate_shares_config(get_clients) -> None: x_secret = torch.Tensor([5.0]) x_share = ShareTensor(data=x_secret) shares_from_share_tensor = MPCTensor.generate_shares(x_share, 2) shares_from_secret = MPCTensor.generate_shares( x_secret, 2, config=Config(encoder_base=2, encoder_precision=16) ) assert sum(shares_from_share_tensor) == sum(shares_from_secret)
def prrs_generate_random_share( self, shape: Union[tuple, torch.Size], ring_size: Optional[str] = None, ) -> Any: """Generates a random share using the generators held by a party. Args: shape (Union[tuple, torch.Size]): Shape for the share. ring_size (str): ring size to generate share. Returns: Any: ShareTensor or ReplicatedSharedTensor """ from sympc.tensor import ReplicatedSharedTensor from sympc.tensor import ShareTensor if ring_size is None: ring_size = self.ring_size else: ring_size = int(ring_size) # 2**64 cannot be serialized. share1, share2 = self._generate_random_share(shape, ring_size) if self.protocol.share_class == ShareTensor: # It has encoder_precision = 0 such that the value would not be encoded share = ShareTensor( data=share1, session_uuid=self.uuid, config=Config(encoder_precision=0), ring_size=ring_size, ) else: share = ReplicatedSharedTensor( shares=[share1, share2], session_uuid=self.uuid, config=Config(encoder_precision=0), ring_size=ring_size, ) return share
def test_ops_share_private(op_str, precision, base) -> None: op = getattr(operator, op_str) x = torch.Tensor([[0.125, -1.25], [-4.25, 4]]) y = torch.Tensor([[4.5, -2.5], [5, 2.25]]) x_share = ReplicatedSharedTensor(shares=[x], config=Config( encoder_base=base, encoder_precision=precision)) y_share = ReplicatedSharedTensor(shares=[y], config=Config( encoder_base=base, encoder_precision=precision)) expected_res = op(x, y) res = op(x_share, y_share) tensor_decoded = res.fp_encoder.decode(res.shares[0]) assert np.allclose(tensor_decoded, expected_res, rtol=base**-precision)
def test_log(get_clients) -> None: clients = get_clients(2) x_secret = torch.Tensor([0.1, 0.5, 2, 5, 10]) x_secret_log = torch.log(x_secret) # with custom precision config = Config(encoder_precision=20) session = Session(parties=clients, config=config) SessionManager.setup_mpc(session) x = MPCTensor(secret=x_secret, session=session) x_log = log(x) assert torch.allclose(x_secret_log, x_log.reconstruct(), atol=1e-1)
def __init__( self, parties: Optional[List[Any]] = None, ring_size: int = 2**64, config: Optional[Config] = None, ttp: Optional[Any] = None, uuid: Optional[UUID] = None, ) -> None: """Initializer for the Session.""" self.uuid = uuid4() if uuid is None else uuid # Each worker will have the rank as the index in the list # Only the party that is the CC (Control Center) will have access # to this self.parties: List[Any] self.nr_parties: int if parties is None: self.parties = [] self.nr_parties = 0 else: self.parties = parties self.nr_parties = len(parties) # Some protocols require a trusted third party # Ex: SPDZ self.trusted_third_party = ttp # The CryptoStore is initialized at each party when it is unserialized self.crypto_store: Optional[Dict[ Any, Any]] = None # TODO: this should be CryptoStore self.protocol: Optional[str] = None self.config = config if config else Config() self.przs_generators: List[List[torch.Generator]] = [] # Those will be populated in the setup_mpc self.rank = -1 self.session_ptrs: List[Session] = [] # Ring size self.tensor_type: Union[torch.dtype] = get_type_from_ring(ring_size) self.ring_size = ring_size self.min_value = -(ring_size) // 2 self.max_value = (ring_size - 1) // 2
def test_ops_public_mul_matrix(get_clients, security, base, precision): parties = get_clients(3) protocol = Falcon(security) config = Config(encoder_base=base, encoder_precision=precision) session = Session(protocol=protocol, parties=parties, config=config) SessionManager.setup_mpc(session) secret = torch.Tensor([[0.125, 1001, 4.82, -1.25], [-4.25, 0.217, 3301, 4]]) value = torch.Tensor([[4.5, 9.25, 3.47, -2.5], [50, 3.17, 5.82, 2.25]]) tensor = MPCTensor(secret=secret, session=session) result = tensor * value expected_res = secret * value assert np.allclose(result.reconstruct(), expected_res, atol=1e-3)
def test_bin_public_xor(get_clients, bit) -> None: clients = get_clients(3) falcon = Protocol.registered_protocols["Falcon"]() session = Session(parties=clients, protocol=falcon) session.config = Config(encoder_base=1, encoder_precision=0) SessionManager.setup_mpc(session) x = torch.tensor([[1, 0], [0, 1]], dtype=torch.bool) b = torch.tensor([bit], dtype=torch.bool) x_share = MPCTensor(secret=x, session=session) result = operator.xor(x_share, b) expected_res = x ^ b assert (result.reconstruct() == expected_res).all()
def test_mul_private(get_clients, security, base, precision): parties = get_clients(3) protocol = Falcon(security) config = Config(encoder_base=base, encoder_precision=precision) session = Session(protocol=protocol, parties=parties, config=config) SessionManager.setup_mpc(session) secret1 = torch.tensor([[-100.25, 0.29, 30.45], [-90.82, 1000, 0.18], [1032.45, -323.18, 15.15]]) secret2 = 8 tensor1 = MPCTensor(secret=secret1, session=session) tensor2 = MPCTensor(secret=secret2, session=session) result = tensor1 * tensor2 expected_res = secret1 * secret2 assert np.allclose(result.reconstruct(), expected_res, atol=1e-3)
def test_fixed_point(precision, base) -> None: x = torch.tensor([1.25, 3.301]) shares = [x, x] rst = ReplicatedSharedTensor(shares=shares, config=Config(encoder_precision=precision, encoder_base=base)) fp_encoder = FixedPointEncoder(precision=precision, base=base) tensor_type = get_type_from_ring(rst.ring_size) for i in range(len(shares)): shares[i] = fp_encoder.encode(shares[i]).to(tensor_type) assert (torch.cat(shares) == torch.cat(rst.shares)).all() for i in range(len(shares)): shares[i] = fp_encoder.decode(shares[i].type(torch.LongTensor)) assert (torch.cat(shares) == torch.cat(rst.decode())).all()
def count_wraps_rand( nr_parties: int, shape: Tuple[int]) -> Tuple[List[ShareTensor], List[ShareTensor]]: """Count wraps random. The Trusted Third Party (TTP) or Crypto provider should generate: - a set of shares for a random number - a set of shares for the number of wraparounds for that number Those shares are used when doing a public division, such that the end result would be the correct one. Args: nr_parties (int): Number of parties shape (Tuple[int]): The shape for the random value Returns: List[List[List[ShareTensor, ShareTensor]]: a list of instaces with the shares for a random integer value and shares for the number of wraparounds that are done when reconstructing the random value """ rand_val = torch.empty(size=shape, dtype=torch.long).random_(generator=ttp_generator) config = Config(encoder_precision=0) r_shares = MPCTensor.generate_shares(secret=rand_val, nr_parties=nr_parties, tensor_type=torch.long, config=config) wraps = count_wraps([share.tensor for share in r_shares]) theta_r_shares = MPCTensor.generate_shares(secret=wraps, nr_parties=nr_parties, tensor_type=torch.long, config=config) # We are always creating only an instance primitives_sequential = [(r_shares, theta_r_shares)] primitives = list( map(list, zip(*map(lambda x: map(list, zip(*x)), primitives_sequential)))) return primitives
def test_ops_public_mul_integer_parties(get_clients, parties, security): config = Config(encoder_base=1, encoder_precision=0) parties = get_clients(parties) protocol = Falcon(security) session = Session(protocol=protocol, parties=parties, config=config) SessionManager.setup_mpc(session) secret = torch.tensor([[-100, 20, 30], [-90, 1000, 1], [1032, -323, 15]]) value = 8 op = getattr(operator, "mul") tensor = MPCTensor(secret=secret, session=session) shares = [op(share, value) for share in tensor.share_ptrs] result = MPCTensor(shares=shares, session=session) assert (result.reconstruct() == (secret * value)).all()
def test_mul_private_matrix(get_clients, security, base, precision): parties = get_clients(3) protocol = Falcon(security) config = Config(encoder_base=base, encoder_precision=precision) session = Session(protocol=protocol, parties=parties, config=config) SessionManager.setup_mpc(session) secret1 = torch.tensor([[-100.25, 20.3, 30.12], [-50.1, 100.217, 1.2], [1032.15, -323.56, 15.15]]) secret2 = torch.tensor([[-1, 0.28, 3], [-9, 10.18, 1], [32, -23, 5]]) tensor1 = MPCTensor(secret=secret1, session=session) tensor2 = MPCTensor(secret=secret2, session=session) result = tensor1 * tensor2 expected_res = secret1 * secret2 assert np.allclose(result.reconstruct(), expected_res, atol=1e-3)
def mul_master( x: MPCTensor, y: MPCTensor, session: Session, op_str: str, kwargs_: Dict[Any, Any], ) -> MPCTensor: """Master method for multiplication. Args: x (MPCTensor): Secret y (MPCTensor): Another secret session (Session): Session the tensors belong to op_str (str): Operation string. kwargs_ (Dict[Any, Any]): Kwargs for some operations like conv2d Returns: result (MPCTensor): Result of the operation. Raises: ValueError: Raised when number of parties are not three. ValueError : Raised when invalid security_type is provided. """ if len(session.parties) != 3: raise ValueError("Falcon requires 3 parties") result = None ring_size = int(x.share_ptrs[0].get_ring_size().get_copy()) conf_dict = x.share_ptrs[0].get_config().get_copy() config = Config(**conf_dict) if session.protocol.security_type == "semi-honest": result = Falcon.mul_semi_honest(x, y, session, op_str, ring_size, config, **kwargs_) elif session.protocol.security_type == "malicious": result = Falcon.mul_malicious(x, y, session, op_str, ring_size, config, **kwargs_) else: raise ValueError("Invalid security_type for Falcon multiplication") result = ABY3.truncate(result, session, ring_size, config) return result
def div_wraps( r_share: ShareTensor, theta_r: ShareTensor, x_share: ShareTensor, z_shares: List[torch.Tensor], y: Union[torch.Tensor, int], ) -> ShareTensor: """From CrypTen Privately computes the number of wraparounds for a set a shares. To do so, we note that: [theta_x] = theta_z + [beta_xr] - [theta_r] - [eta_xr] Where: [theta_x] is the wraps for a variable x [beta_xr] is the differential wraps for variables x and r [eta_xr] is the plaintext wraps for variables x and r Note: Since [eta_xr] = 0 with probability 1 - |x| / Q for modulus Q, we can make the assumption that [eta_xr] = 0 with high probability. Args: r_share (ShareTensor): share for a random variable "r" theta_r (ShareTensor): share for the number of wraparounds for "r" x_share (ShareTensor): shares for which we want to compute the number of wraparounds z_shares (List[torch.Tensor]): list of shares for a random value y (Union[torch.Tensor, int]): the number/tensor by which we divide Returns: ShareTensor representing the number of wraparounds """ session = get_session(str(r_share.session_uuid)) beta_xr = count_wraps([x_share.tensor, r_share.tensor]) theta_x = ShareTensor(config=Config(encoder_precision=0)) theta_x.tensor = beta_xr - theta_r.tensor if session.rank == 0: theta_z = count_wraps(z_shares) theta_x.tensor += theta_z x_share.tensor //= y return theta_x
def test_session_custom_init() -> None: config = Config() session = Session(parties=["alice", "bob"], ring_size=2**32, config=config, ttp="TTP") assert session.uuid is None assert session.parties == ["alice", "bob"] assert session.trusted_third_party == "TTP" assert session.crypto_store is None assert session.protocol is not None assert session.config == config assert session.przs_generators == [] assert session.rank == -1 assert session.session_ptrs == [] assert session.tensor_type == get_type_from_ring(2**32) assert session.ring_size == 2**32 assert session.min_value == -(2**32) // 2 assert session.max_value == (2**32 - 1) // 2
def test_truncation_algorithm1(get_clients, base, precision) -> None: parties = get_clients(3) falcon = Falcon("semi-honest") config = Config(encoder_base=base, encoder_precision=precision) session = Session(parties=parties, protocol=falcon, config=config) SessionManager.setup_mpc(session) x = torch.tensor([[1.24, 4.51, 6.87], [7.87, 1301, 541]]) x_mpc = MPCTensor(secret=x, session=session) result = ABY3.truncate(x_mpc, session, session.ring_size, session.config) fp_encoder = FixedPointEncoder(base=session.config.encoder_base, precision=session.config.encoder_precision) expected_res = x_mpc.reconstruct(decode=False) // fp_encoder.scale expected_res = fp_encoder.decode(expected_res) assert np.allclose(result.reconstruct(), expected_res, atol=1e-3)
def proto2object(proto: ShareTensor_PB) -> ShareTensor: if proto.session_uuid: session = sympc.session.get_session(proto.session_uuid) if session is None: raise ValueError( f"The session {proto.session_uuid} could not be found") config = dataclasses.asdict(session.config) else: config = syft.deserialize(proto.config, from_proto=True) data = protobuf_tensor_deserializer(proto.tensor.tensor) share = ShareTensor(data=None, config=Config(**config)) if proto.session_uuid: share.session_uuid = UUID(proto.session_uuid) # Manually put the tensor since we do not want to re-encode it share.tensor = data return share
def bit_decomposition_ttp(x: MPCTensor, session: Session) -> List[MPCTensor]: """Perform ABY3 bit decomposition using orchestrator as ttp. Args: x (MPCTensor): Arithmetic shares of secret. session (Session): session the share belongs to. Returns: b_sh (List[MPCTensor]): Returns binary shares of each bit of the secret. TODO: We should modify to use parallel prefix adder, which requires multiprocessing. """ # Decoding is not done as they are shares of PRRS. tensor = x.reconstruct(decode=False) b_sh: List[MPCTensor] = [] # binary shares of bits ring_size = session.ring_size shares_sum = ReplicatedSharedTensor.shares_sum ring_bits = get_nr_bits(ring_size) for idx in range(ring_bits): bit_mask = torch.ones(tensor.shape, dtype=tensor.dtype) << idx secret = (tensor & bit_mask).type(torch.bool) r1 = torch.empty(size=tensor.shape, dtype=torch.bool).random_(generator=gen) r2 = torch.empty(size=tensor.shape, dtype=torch.bool).random_(generator=gen) r3 = shares_sum([secret, r1, r2], ring_size=2) shares = [r1, r2, r3] config = Config(encoder_base=1, encoder_precision=0) sh_ptr = ReplicatedSharedTensor.distribute_shares(shares=shares, session=session, ring_size=2, config=config) b_mpc = MPCTensor(shares=sh_ptr, session=session, shape=tensor.shape) b_sh.append(b_mpc) return b_sh