Source code for distributed_keygen.paillier_shared_key

"""
Paillier secret key that is shared amongst several parties.
"""

from typing import Dict

from tno.mpc.encryption_schemes.paillier import PaillierCiphertext
from tno.mpc.encryption_schemes.shamir import IntegerShares
from tno.mpc.encryption_schemes.templates.asymmetric_encryption_scheme import SecretKey
from tno.mpc.encryption_schemes.utils import mod_inv, pow_mod

from .utils import mult_list


[docs]class PaillierSharedKey(SecretKey): """ Class containing relevant attributes and methods of a shared paillier key. """
[docs] def __init__( self, n: int, t: int, player_id: int, share: IntegerShares, theta: int ) -> None: """ Initializes a Paillier shared key. :param n: modulus of the DistributedPaillier scheme this secret key belongs to :param t: corruption_threshold of the secret sharing :param player_id: the index of the player to whom the key belongs :param share: secret sharing of the exponent used during decryption :param theta: Value used in the computation of a full decryption after partial decryptions have been obtained. We refer to the paper for more details """ super().__init__() self.share = share self.n = n self.n_square = n * n self.t = t self.player_id = player_id self.theta = theta
[docs] def partial_decrypt(self, ciphertext: PaillierCiphertext) -> int: """ Function that does local computations to get a partial decryption of a ciphertext. :param ciphertext: ciphertext to be partially decrypted :raise TypeError: If the given ciphertext is not of type PaillierCiphertext. :raise ValueError: If the ciphertext is encrypted against a different key. :return: partial decryption of ciphertext """ if not isinstance(ciphertext, PaillierCiphertext): raise TypeError( f"Expected ciphertext to be a PaillierCiphertext not: {type(ciphertext)}" ) if self.n != ciphertext.scheme.public_key.n: raise ValueError("encrypted against a different key!") ciphertext_value = ciphertext.value n_fac = self.share.n_fac other_honest_players = [ i + 1 for i in range(self.share.degree + 1) if i + 1 != self.player_id ] # NB: Here the reconstruction set is implicit defined, but any # large enough subset of shares will do. # reconstruction_shares = {key: shares[key] for key in list(shares.keys())[:degree + 1]} lagrange_interpol_enumerator = mult_list(other_honest_players) lagrange_interpol_denominator = mult_list( [(j - self.player_id) for j in other_honest_players] ) exp = ( n_fac * lagrange_interpol_enumerator * self.share.shares[self.player_id] ) // lagrange_interpol_denominator # Notice that the partial decryption is already raised to the power given # by the Lagrange interpolation coefficient if exp < 0: ciphertext_value = mod_inv(ciphertext_value, self.n_square) exp = -exp partial_decryption = pow_mod(ciphertext_value, exp, self.n_square) return partial_decryption
[docs] def decrypt(self, partial_dict: Dict[int, int]) -> int: r""" Function that uses partial decryptions of other parties to reconstruct a full decryption of the initial ciphertext. :param partial_dict: dictionary containing the partial decryptions of each party :raise ValueError: Either in case not enough shares are known in order to decrypt. Or when the combined decryption minus one is not divisible by $N$. This last case is most likely caused by the fact the ciphertext that is being decrypted, differs between parties. :return: full decryption """ partial_decryptions = [ partial_dict[i + 1] for i in range(self.share.degree + 1) ] if len(partial_decryptions) < self.share.degree + 1: raise ValueError("Not enough shares.") combined_decryption = ( mult_list(partial_decryptions[: self.share.degree + 1]) % self.n_square ) if (combined_decryption - 1) % self.n != 0: raise ValueError( "Combined decryption minus one is not divisible by N. This might be caused by the " "fact that the ciphertext that is being decrypted, differs between the parties." ) message = ( (combined_decryption - 1) // self.n * mod_inv(self.theta, self.n) ) % self.n return message
[docs] def __str__(self) -> str: """ Utility function to represent the local share of the private key as a string. :return: String representation of this private key part. """ return str( { "priv_shared_key": { "n": self.n, "t": self.t, "player_id": self.player_id, "theta": self.theta, "share": self.share.serialize(), } } )