コード例 #1
0
def compute_partial_re_encryption_key(
        old_share: KeyShare, old_lc: LagrangeCoefficient, new_share: KeyShare,
        new_lc: LagrangeCoefficient) -> PartialReEncryptionKey:
    """
    Compute a partial re-encryption key from a participants old and new share.

    :param old_share: the participants "old" key share
    :param old_lc: "old" lagrange coefficient provided by the coordinating party
    :param new_share: the participants "new" key share
    :param new_lc: "new" lagrange coefficient provided by the coordinating party
    :return: the partial re-encryption key
    """
    curve_params = old_share.curve_params

    if curve_params != new_share.curve_params:
        raise ThresholdCryptoError(
            'Differing curves not supported for re-encryption!')

    if old_share.x != old_lc.participant_index:
        raise ThresholdCryptoError(
            'Lagrange coefficient for OLD share was computed for other participant index'
        )

    if new_share.x != new_lc.participant_index:
        raise ThresholdCryptoError(
            'Lagrange coefficient for NEW share was computed for other participant index'
        )

    partial_re_key = (new_share.y * new_lc.coefficient -
                      old_share.y * old_lc.coefficient) % curve_params.order

    return PartialReEncryptionKey(partial_re_key, curve_params)
コード例 #2
0
    def receive_F_ij_value(self, F_ij: DkgFijValue):
        """
        Receive the F_ij value of another participant.

        :param F_ij: the received F_ij value
        """
        # implicit check for successful receival of all commitments
        self.compute_public_key()

        source_id = F_ij.source_participant_id
        len_F_ij = len(F_ij.F_ij)

        if source_id not in self.all_participant_ids:
            raise ThresholdCryptoError(
                "Received F_ij values from unknown participant id {}".format(
                    source_id))

        if source_id == self.id:
            raise ThresholdCryptoError(
                "Received own F_ij values - don't do this")

        if len_F_ij != self.threshold_params.t:
            raise ThresholdCryptoError(
                "List of F_ij values from participant {} has length {} != {} = t"
                .format(source_id, len_F_ij, self.threshold_params.t))

        if source_id not in self._received_F:
            self._received_F[source_id] = F_ij
        else:
            raise ThresholdCryptoError(
                "F_ij values from participant {} already received".format(
                    source_id))
コード例 #3
0
def _decrypt_message(
    partial_decryptions: List[PartialDecryption],
    encrypted_message: EncryptedMessage,
) -> str:
    # this method does not contain the check for given number of partial decryptions to allow testing the failing decryption
    curve_params = partial_decryptions[0].curve_params
    for partial_key in partial_decryptions:
        if partial_key.curve_params != curve_params:
            raise ThresholdCryptoError(
                "Varying curve parameters found in partial re-encryption keys")

    key_point = _combine_shares(
        partial_decryptions,
        encrypted_message,
        curve_params,
    )
    point_bytes = _key_bytes_from_point(key_point)

    try:
        key = nacl.hash.blake2b(point_bytes,
                                digest_size=nacl.secret.SecretBox.KEY_SIZE,
                                encoder=nacl.encoding.RawEncoder)
        box = nacl.secret.SecretBox(key)
        encoded_plaintext = box.decrypt(encrypted_message.ciphertext)
    except nacl.exceptions.CryptoError as e:
        raise ThresholdCryptoError('Message decryption failed. Internal: ' +
                                   str(e))

    return str(encoded_plaintext, 'utf-8')
コード例 #4
0
def lagrange_coefficient_for_key_share_indices(
        key_share_indices: List[int], p_idx: int,
        curve_params: CurveParameters) -> LagrangeCoefficient:
    """
    Create the ith Lagrange coefficient for a list of key shares.

    :param key_share_indices: the used indices for the participants key shares
    :param curve_params: the used curve parameters
    :param p_idx: the participant index (= the shares x value), the Lagrange coefficient belongs to
    :return:
    """
    if p_idx not in key_share_indices:
        raise ThresholdCryptoError(
            "Participant index {} not found in used indices {} for computation of Lagrange coefficient"
            .format(p_idx, key_share_indices))

    idx_len = len(key_share_indices)
    i = key_share_indices.index(p_idx)

    def x(idx):
        return key_share_indices[idx]

    tmp = [(-x(j) * number.prime_mod_inv(x(i) - x(j), curve_params.order))
           for j in range(0, idx_len) if not j == i]
    coefficient = number.prod(tmp) % curve_params.order  # lambda_i

    return LagrangeCoefficient(p_idx, key_share_indices, coefficient)
コード例 #5
0
 def _unchecked_s_ij_value_for_participant(
         self, target_participant_id: ParticipantId) -> DkgSijValue:
     # This method is provided so that it can be used for the own s_ij value computation in the __init__
     # method without performing the check in s_ij_value_for_participant.
     if target_participant_id not in self.all_participant_ids:
         raise ThresholdCryptoError(
             "Participant id {} not present in known participant ids".
             format(target_participant_id))
     else:
         return DkgSijValue(self.id, target_participant_id,
                            self._local_sij[target_participant_id])
コード例 #6
0
    def open_commitment(self) -> DkgOpenCommitment:
        """
        The participants open commitment to the "public key share" h_i, which can be evaluated.

        :return: the open commitment
        """
        if len(self._received_closed_commitments) != self.threshold_params.n:
            raise ThresholdCryptoError(
                "Open commitment is just accessible when all other closed commitments were received"
            )

        return self._unchecked_open_commitment()
コード例 #7
0
    def receive_sij(self, received_sij: DkgSijValue):
        """
        Receive the s_ij value of another participant.

        :param received_sij: the received s_ij value
        """
        source_id = received_sij.source_participant_id
        target_id = received_sij.target_participant_id
        sij = received_sij.s_ij

        if source_id not in self.all_participant_ids:
            raise ThresholdCryptoError(
                "Received s_ij value from unknown participant id {}".format(
                    source_id))

        if source_id == self.id:
            raise ThresholdCryptoError(
                "Received own s_ij value - don't do this")

        if target_id != self.id:
            raise ThresholdCryptoError(
                "Received s_ij value for foreign participant (own id={}, target id={})"
                .format(self.id, target_id))

        if source_id not in self._received_sij:
            self._received_sij[source_id] = received_sij
        else:
            raise ThresholdCryptoError(
                "s_ij value for participant {} already received".format(
                    source_id))

        # verify received F values
        s_ijP = sij * self.curve_params.P
        F_list = [(self.id**l) * F_jl
                  for l, F_jl in enumerate(self._received_F[source_id].F_ij)]
        F_sum = number.ecc_sum(F_list)

        if s_ijP != F_sum:
            raise ThresholdCryptoError(
                "F verification failed for participant {}".format(source_id))
コード例 #8
0
def combine_partial_re_encryption_keys(
        partial_keys: List[PartialReEncryptionKey], old_public_key: PublicKey,
        new_public_key: PublicKey, old_threshold_params: ThresholdParameters,
        new_threshold_params: ThresholdParameters) -> ReEncryptionKey:
    """
    Combine a number of partial re-encryption keys yielding the re-encryption key.

    :param partial_keys: The partial keys as provided by participants
    :param old_public_key: the public key of the old access structure
    :param new_public_key: the public key of the new access structure
    :param old_threshold_params: the threshold parameters of the old access structure
    :param new_threshold_params: the threshold parameters of the new access structure
    :return: the re-encryption key
    """
    # TODO check threshold parameters

    max_t = max(old_threshold_params.t, new_threshold_params.t)

    if len(partial_keys) < max_t:
        raise ThresholdCryptoError(
            "Not enough partial re-encryption keys given")

    curve_params = partial_keys[0].curve_params
    for partial_key in partial_keys:
        if partial_key.curve_params != curve_params:
            raise ThresholdCryptoError(
                "Varying curve parameters found in partial re-encryption keys")

    re_key = sum([k.partial_key for k in partial_keys]) % curve_params.order

    # check that the proxy key is valid using the given public keys
    # proxy_key * P =?= new_pub - old_pub
    checkval1 = curve_params.P * re_key
    checkval2 = new_public_key.Q + (-old_public_key.Q)

    if checkval1 != checkval2:
        raise ThresholdCryptoError(
            "The combined proxy key is invalid for given public keys.")

    return ReEncryptionKey(re_key, curve_params)
コード例 #9
0
    def receive_closed_commitment(self, commitment: DkgClosedCommitment):
        """
        Receive a closed commitment to the "public key share" h_i of another participant.

        :param commitment: the received commitment
        """
        source_id = commitment.participant_id

        if source_id not in self.all_participant_ids:
            raise ThresholdCryptoError(
                "Received closed commitment from unknown participant id {}".
                format(source_id))

        if source_id == self.id:
            raise ThresholdCryptoError(
                "Received own closed commitment - don't do this")

        if source_id not in self._received_closed_commitments:
            self._received_closed_commitments[source_id] = commitment
        else:
            raise ThresholdCryptoError(
                "Closed commitment from participant {} already received".
                format(source_id))
コード例 #10
0
    def s_ij_value_for_participant(
            self, target_participant_id: ParticipantId) -> DkgSijValue:
        """
        The s_ij value from Pedersens DKG protocol for ONE other particular participant.
        This value has to be sent SECRETLY to the target participant, which is not covered by this library for now.

        :param target_participant_id: the id of the target participant
        """
        if len(self._received_F) != self.threshold_params.n:
            raise ThresholdCryptoError(
                "s_ij values are just accessible when all other F_ij values were received"
            )

        return self._unchecked_s_ij_value_for_participant(
            target_participant_id)
コード例 #11
0
    def compute_public_key(self) -> PublicKey:
        """
        Compute the public key from received commitment values.

        :return: the public key
        """
        if len(self._received_open_commitments) != self.threshold_params.n:
            raise ThresholdCryptoError("Not all commitments were received")

        participants_h_i = [
            c.h_i for c in self._received_open_commitments.values()
        ]
        h = number.ecc_sum(participants_h_i)

        return PublicKey(h, self.curve_params)
コード例 #12
0
def decrypt_message(partial_decryptions: List[PartialDecryption],
                    encrypted_message: EncryptedMessage,
                    threshold_params: ThresholdParameters) -> str:
    """
    Decrypt a message using the combination of at least t partial decryptions. Similar to the encryption process
    the hybrid approach is used for decryption.

    :param partial_decryptions: at least t partial decryptions
    :param encrypted_message: the encrapted message to be decrypted
    :param threshold_params: the used threshold parameters
    :return: the decrypted message
    """
    if len(partial_decryptions) < threshold_params.t:
        raise ThresholdCryptoError('less than t partial decryptions given')

    return _decrypt_message(partial_decryptions, encrypted_message)
コード例 #13
0
    def compute_share(self) -> KeyShare:
        """
        Compute the participants key share from values obtained during the DKG protocol.

        :return: the final key share after the DKG protocol
        """
        if len(self._received_sij) != self.threshold_params.n:
            raise ThresholdCryptoError(
                "Received less s_ij values than necessary: {} != {} = n".
                format(len(self._received_sij), self.threshold_params.n))

        self._s_i = sum(
            rs.s_ij
            for rs in self._received_sij.values()) % self.curve_params.order
        self.key_share = KeyShare(self.id, self._s_i, self.curve_params)

        return self.key_share
コード例 #14
0
def encrypt_message(message: str, public_key: PublicKey) -> EncryptedMessage:
    """
    Encrypt a message using a public key. A hybrid encryption approach is used to include advantages of symmetric
    encryption (fast, independent of message-length, integrity-preserving by using AE-scheme).
    Internally a combination of Salsa20 and Poly1305 from the cryptographic library NaCl is used.

    :param message: the message to be encrypted
    :param public_key: the public key
    :return: the encrypted message
    """
    curve_params = public_key.curve_params
    encoded_message = bytes(message, 'utf-8')

    # Create random subgroup element and use its hash as symmetric key to prevent
    # attacks described in "Why Textbook ElGamal and RSA Encryption Are Insecure"
    # by Boneh et. al.
    r = number.random_in_range(1, curve_params.order)
    key_point = r * curve_params.P
    point_bytes = _key_bytes_from_point(key_point)

    try:
        symmetric_key = nacl.hash.blake2b(
            point_bytes,
            digest_size=nacl.secret.SecretBox.KEY_SIZE,
            encoder=nacl.encoding.RawEncoder)
        # Use derived symmetric key to encrypt the message
        box = nacl.secret.SecretBox(symmetric_key)
        encrypted = box.encrypt(encoded_message)
    except nacl.exceptions.CryptoError as e:
        print('Encryption failed: ' + str(e))
        raise ThresholdCryptoError('Message encryption failed.')

    # Use threshold scheme to encrypt the curve point used as hash input to derive the symmetric key
    C1, C2 = _encrypt_key_point(key_point, public_key.Q, curve_params)

    return EncryptedMessage(C1, C2, encrypted)
コード例 #15
0
    def receive_open_commitment(self, open_commitment: DkgOpenCommitment):
        """
        Receive an open (evaluatable) commitment to the "public key share" h_i of another participant.

        :param open_commitment: the received commitment
        """
        source_id = open_commitment.participant_id

        if source_id not in self.all_participant_ids:
            raise ThresholdCryptoError(
                "Received open commitment from unknown participant id {}".
                format(source_id))

        if source_id == self.id:
            raise ThresholdCryptoError(
                "Received own open commitment - don't do this")

        if source_id not in self._received_closed_commitments:
            raise ThresholdCryptoError(
                "Received open commitment from participant id {} withput received closed commitment"
                .format(source_id))

        closed_commitment = self._received_closed_commitments[source_id]

        if closed_commitment.commitment != open_commitment.commitment:
            raise ThresholdCryptoError(
                "Open and close commitment values differ for participant {}".
                format(source_id))

        if self._compute_commitment(
                open_commitment.r,
                open_commitment.h_i) != closed_commitment.commitment:
            raise ThresholdCryptoError(
                "Invalid commitment for participant {}".format(source_id))

        if source_id not in self._received_open_commitments:
            self._received_open_commitments[source_id] = open_commitment
        else:
            raise ThresholdCryptoError(
                "Open commitment from participant {} already received".format(
                    source_id))
コード例 #16
0
    def __init__(self, own_id: ParticipantId,
                 all_participant_ids: List[ParticipantId],
                 curve_params: CurveParameters,
                 threshold_params: ThresholdParameters):
        """
        Initialize a participant.

        :param own_id: the id of this participant.
        As this id is used as the final shares x value, it's important that participants use distinct ids.
        :param all_participant_ids: a list of all
        :param curve_params: the curve parameters used
        :param threshold_params: the required threshold parameters
        """
        if len(set(all_participant_ids)) != threshold_params.n:
            raise ThresholdCryptoError(
                "List of distinct participant ids has length {} != {} = n".
                format(len(all_participant_ids), threshold_params.n))

        if own_id not in all_participant_ids:
            raise ThresholdCryptoError(
                "Own id must be contained in all participant ids")

        self.all_participant_ids: List[ParticipantId] = all_participant_ids
        self.id: ParticipantId = own_id
        self.curve_params: CurveParameters = curve_params
        self.threshold_params: ThresholdParameters = threshold_params

        self._x_i: int = number.random_in_range(0, curve_params.order)
        self._h_i: ECC.EccPoint = self._x_i * curve_params.P
        self._polynom: number.PolynomMod = number.PolynomMod.create_random_polynom(
            self._x_i, self.threshold_params.t - 1, curve_params.order)

        # calculate own F_ij values
        self._local_F_ij: List[ECC.EccPoint] = []
        for coeff in self._polynom.coefficients:
            self._local_F_ij.append(coeff * curve_params.P)

        # calculate own s_ij values
        self._local_sij: Dict[ParticipantId, int] = {}
        for p_id in self.all_participant_ids:
            s_ij = self._polynom.evaluate(p_id)
            self._local_sij[p_id] = s_ij

        # random value for commitment of h_i
        rand_int = random.getrandbits(self._COMMITMENT_RANDOM_BITS)
        self._commitment_random: bytes = number.int_to_bytes(rand_int)
        self._commitment: bytes = self._compute_commitment(
            self._commitment_random, self._h_i)

        self._received_closed_commitments: Dict[ParticipantId,
                                                DkgClosedCommitment] = {
                                                    self.id:
                                                    self.closed_commmitment()
                                                }
        self._received_open_commitments: Dict[
            ParticipantId, DkgOpenCommitment] = {
                self.id: self._unchecked_open_commitment()
            }
        self._received_F: Dict[ParticipantId, DkgFijValue] = {
            self.id: self.F_ij_value()
        }
        self._received_sij: Dict[ParticipantId, DkgSijValue] = {
            self.id: self._unchecked_s_ij_value_for_participant(self.id)
        }

        self._s_i: Optional[int] = None
        self.key_share: Optional[KeyShare] = None