feat: init repo
This commit is contained in:
162
encoding.py
Normal file
162
encoding.py
Normal file
@@ -0,0 +1,162 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user