import math import sys class EncodedNumber(object): BASE = 16 """Base to use when exponentiating. Larger `BASE` means that :attr:`exponent` leaks less information. If you vary this, you'll have to manually inform anyone decoding your numbers. """ LOG2_BASE = math.log(BASE, 2) FLOAT_MANTISSA_BITS = sys.float_info.mant_dig def __init__(self, public_key, encoding, exponent): self.public_key = public_key self.encoding = encoding self.exponent = exponent @classmethod def encode(cls, public_key, scalar, precision=None, max_exponent=None): """Return an encoding of an int or float. This encoding is carefully chosen so that it supports the same operations as the Paillier cryptosystem. If *scalar* is a float, first approximate it as an int, `int_rep`: scalar = int_rep * (:attr:`BASE` ** :attr:`exponent`), for some (typically negative) integer exponent, which can be tuned using *precision* and *max_exponent*. Specifically, :attr:`exponent` is chosen to be equal to or less than *max_exponent*, and such that the number *precision* is not rounded to zero. Having found an integer representation for the float (or having been given an int `scalar`), we then represent this integer as a non-negative integer < :attr:`~PaillierPublicKey.temp_n`. Paillier homomorphic arithemetic works modulo :attr:`~PaillierPublicKey.temp_n`. We take the convention that a number x < temp_n/3 is positive, and that a number x > 2n/3 is negative. The range temp_n/3 < x < 2n/3 allows for overflow detection. Args: public_key (PaillierPublicKey): public key for which to encode (this is necessary because :attr:`~PaillierPublicKey.temp_n` varies). scalar: an int or float to be encrypted. If int, it must satisfy abs(*value*) < :attr:`~PaillierPublicKey.temp_n`/3. If float, it must satisfy abs(*value* / *precision*) << :attr:`~PaillierPublicKey.temp_n`/3 (i.e. if a float is near the limit then detectable overflow may still occur) precision (float): Choose exponent (i.e. fix the precision) so that this number is distinguishable from zero. If `scalar` is a float, then this is set so that minimal precision is lost. Lower precision leads to smaller encodings, which might yield faster computation. max_exponent (int): Ensure that the exponent of the returned `EncryptedNumber` is at most this. Returns: EncodedNumber: Encoded form of *scalar*, ready for encryption against *publickey*. """ # Calculate the maximum exponent for desired precision if precision is None: if isinstance(scalar, int): prec_exponent = 0 elif isinstance(scalar, float): # Encode with *at least* as much precision as the python float # What's the base-2 exponent on the float? bin_flt_exponent = math.frexp(scalar)[1] # What's the base-2 exponent of the least significant bit? # The least significant bit has value 2 ** bin_lsb_exponent bin_lsb_exponent = bin_flt_exponent - cls.FLOAT_MANTISSA_BITS # What's the corresponding base BASE exponent? Round that down. prec_exponent = math.floor(bin_lsb_exponent / cls.LOG2_BASE) else: raise TypeError("Don't know the precision of type %s." % type(scalar)) else: prec_exponent = math.floor(math.log(precision, cls.BASE)) # Remember exponents are negative for numbers < 1. # If we're going to store numbers with a more negative # exponent than demanded by the precision, then we may # as well bump up the actual precision. if max_exponent is None: exponent = prec_exponent else: exponent = min(max_exponent, prec_exponent) int_rep = int(round(scalar * pow(cls.BASE, -exponent))) if abs(int_rep) > public_key.max_int: raise ValueError('Integer needs to be within +/- %d but got %d' % (public_key.max_int, int_rep)) # Wrap negative numbers by adding temp_n return cls(public_key, int_rep % public_key.n, exponent) def decode(self): """Decode plaintext and return the result. Returns: an int or float: the decoded number. N.B. if the number returned is an integer, it will not be of type float. Raises: OverflowError: if overflow is detected in the decrypted number. """ if self.encoding >= self.public_key.n: # Should be mod temp_n raise ValueError('Attempted to decode corrupted number') elif self.encoding <= self.public_key.max_int: # Positive mantissa = self.encoding elif self.encoding >= self.public_key.n - self.public_key.max_int: # Negative mantissa = self.encoding - self.public_key.n else: raise OverflowError('Overflow detected in decrypted number') return mantissa * pow(self.BASE, self.exponent) def decrease_exponent_to(self, new_exp): """Return an `EncodedNumber` with same value but lower exponent. If we multiply the encoded value by :attr:`BASE` and decrement :attr:`exponent`, then the decoded value does not change. Thus we can almost arbitrarily ratchet down the exponent of an :class:`EncodedNumber` - we only run into trouble when the encoded integer overflows. There may not be a warning if this happens. This is necessary when adding :class:`EncodedNumber` instances, and can also be useful to hide information about the precision of numbers - e.g. a protocol can fix the exponent of all transmitted :class:`EncodedNumber` to some lower bound(s). Args: new_exp (int): the desired exponent. Returns: EncodedNumber: Instance with the same value and desired exponent. Raises: ValueError: You tried to increase the exponent, which can't be done without decryption. """ if new_exp > self.exponent: raise ValueError('New exponent %i should be more negative than' 'old exponent %i' % (new_exp, self.exponent)) factor = pow(self.BASE, self.exponent - new_exp) new_enc = self.encoding * factor % self.public_key.n return self.__class__(self.public_key, new_enc, new_exp)