Example #1
0
class SMCParty:
    """
    A client that executes an SMC protocol to collectively compute a value of an expression together
    with other clients.

    Attributes:
        client_id: Identifier of this client
        server_host: hostname of the server
        server_port: port of the server
        protocol_spec (ProtocolSpec): Protocol specification
        value_dict (dict): Dictionary assigning values to secrets belonging to this client.
    """

    def __init__(
            self,
            client_id: str,
            server_host: str,
            server_port: int,
            protocol_spec: ProtocolSpec,
            value_dict: Dict[Secret, int]
    ):

        self.comm = Communication(server_host, server_port, client_id)

        self.client_id = client_id
        self.protocol_spec = protocol_spec
        self.value_dict = value_dict

        self.secrets = list(value_dict.keys())

        self.own_shares = dict()

    def run(self) -> int:
        """
        The method the client use to do the SMC.
        """

        start = time.time() * 1000

        # Share secrets across participants
        for s in self.secrets:
            shares = share_secret(self.value_dict[s], len(self.protocol_spec.participant_ids))

            for i, pid in enumerate(self.protocol_spec.participant_ids):

                if pid != self.client_id:
                    # Publish shares for other participants
                    self.comm.send_private_message(pid, str(s.get_id_int()), str(shares[i].value))
                else:
                    # Keep own share in dict
                    self.own_shares[str(s.get_id_int())] = shares[i]

        # compute locally share of the final value
        final_share = self.process_expression(self.protocol_spec.expr).value

        # send final share to others
        self.comm.publish_message("final", str(final_share))

        # put every share of the circuit together
        final_result = Share(final_share)

        for pid in self.protocol_spec.participant_ids:

            if pid != self.client_id:
                remote_share = self.comm.retrieve_public_message(pid, "final")

                while remote_share is None:  # retry if not yet here

                    remote_share = self.comm.retrieve_public_message(pid, "final")
                final_result += Share(int(remote_share))

        stop = time.time() * 1000

        total_time = stop - start
        computation_time = total_time - self.comm.network_delays

        total_bytes_sent = self.comm.bytes_sent
        total_bytes_received = self.comm.bytes_received

        # TODO: counted only bytes of the message, should count the bytes of the whole packet?

        # TODO: write metrics in a file
        if self.client_id == self.protocol_spec.participant_ids[0]:
            res_file = open("metrics/mul_secret/"+self.client_id + "_metrics.txt", "a")
            res_file.write(str(total_time) + "," + str(computation_time) + "," + str(total_bytes_sent) + "," + str(
                total_bytes_received)+"\n")
            res_file.close()

        return final_result.value

    # Suggestion: To process expressions, make use of the *visitor pattern* like so:
    def process_expression(
            self,
            expr: Expression,
            secret_in_mult=False
    ) -> Share:

        if isinstance(expr, Addition):  # if expr is an addition operation:
            return self.process_expression(expr.e1) + self.process_expression(expr.e2)

        if isinstance(expr, Substraction):  # if expr is an addition operation:
            return self.process_expression(expr.e1) - self.process_expression(expr.e2)

        if isinstance(expr, Multiplication):
            if self.contains_secret(expr.e1) and self.contains_secret(expr.e2):

                x = self.process_expression(expr.e1, True)
                y = self.process_expression(expr.e2, True)

                x_minus_a, y_minus_b, c = self.get_beaver_shares(x, y, expr)

                if self.client_id == self.protocol_spec.participant_ids[0]:  # participant 0 add the constant
                    return c + x * y_minus_b + y * x_minus_a - x_minus_a * y_minus_b
                else:
                    return c + x * y_minus_b + y * x_minus_a
            else:
                secret_mult = self.contains_secret(expr.e1) or self.contains_secret(expr.e2) or secret_in_mult

                return self.process_expression(expr.e1, secret_mult) * self.process_expression(expr.e2, secret_mult)

        if isinstance(expr, Secret):
            if str(expr.get_id_int()) in self.own_shares.keys():
                return self.own_shares[str(expr.get_id_int())]
            else:
                return Share(self.search_share(expr.get_id_int()))

        if isinstance(expr, Scalar):
            # scalar should only be added by one participant in case of addition
            if (self.protocol_spec.participant_ids[0] != self.client_id) and (not secret_in_mult):
                return Share(0)
            else:
                return Share(expr.value)

    def search_share(self, expr_id) -> int:
        """Search for corresponding secret on the server"""
        return int(self.comm.retrieve_private_message(str(expr_id)))
      

    def get_beaver_shares(self, x, y, expr):

        op_id_str = str(expr.get_id_int())

        a, b, c = self.comm.retrieve_beaver_triplet_shares(op_id_str)

        # send to others shares of x-a and y-b
        x_minus_a_share = x - Share(a)
        y_minus_b_share = y - Share(b)

        self.comm.publish_message(self.client_id + "x_minus_a_" + op_id_str, str(x_minus_a_share.value))
        self.comm.publish_message(self.client_id + "y_minus_b_" + op_id_str, str(y_minus_b_share.value))

        # reconstruct x-a and y-b

        x_shares = x_minus_a_share
        y_shares = y_minus_b_share

        for pid in self.protocol_spec.participant_ids:

            if pid != self.client_id:

                curr_x = self.comm.retrieve_public_message(pid, pid + "x_minus_a_" + op_id_str)
                curr_y = self.comm.retrieve_public_message(pid, pid + "y_minus_b_" + op_id_str)

                while curr_x is None:  # wait for others to upload their shares
                    curr_x = self.comm.retrieve_public_message(pid, pid + "x_minus_a_" + op_id_str)

                while curr_y is None:
                    curr_y = self.comm.retrieve_public_message(pid, pid + "y_minus_b_" + op_id_str)

                x_shares += Share(int(curr_x))
                y_shares += Share(int(curr_y))

        return x_shares, y_shares, Share(c)

    def contains_secret(self, expr):
        if isinstance(expr, Secret):
            return True
        elif isinstance(expr, Scalar):
            return False
        else:
            return self.contains_secret(expr.e1) or self.contains_secret(expr.e2)