Skip to content

Commit 0318db1

Browse files
committed
Move encoding into own file
1 parent 553f431 commit 0318db1

File tree

6 files changed

+288
-282
lines changed

6 files changed

+288
-282
lines changed

docs/phe.rst

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,22 @@ Paillier
1111
:undoc-members:
1212
:show-inheritance:
1313

14+
Encoding
15+
--------
1416

15-
Utilities
16-
---------
17-
18-
19-
.. automodule:: phe.util
17+
.. automodule:: phe.encoding
2018
:members:
2119
:undoc-members:
2220
:show-inheritance:
2321

2422

25-
CLI
26-
----
23+
24+
Utilities
25+
---------
2726

2827

29-
.. automodule:: phe.command_line
28+
.. automodule:: phe.util
3029
:members:
3130
:undoc-members:
3231
:show-inheritance:
32+

examples/alternative_base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
#!/usr/bin/env python3.4
22
import math
3+
4+
import phe.encoding
35
from phe import paillier
46

57

6-
class ExampleEncodedNumber(paillier.EncodedNumber):
8+
class ExampleEncodedNumber(phe.encoding.EncodedNumber):
79
BASE = 64
810
LOG2_BASE = math.log(BASE, 2)
911

phe/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import pkg_resources
22

3+
from phe.encoding import EncodedNumber
34
from phe.paillier import generate_paillier_keypair
4-
from phe.paillier import EncodedNumber
55
from phe.paillier import EncryptedNumber
66
from phe.paillier import PaillierPrivateKey, PaillierPublicKey
77
from phe.paillier import PaillierPrivateKeyring
8+
89
import phe.util
910

1011
try:

phe/encoding.py

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
import math
2+
import sys
3+
4+
5+
class EncodedNumber(object):
6+
"""Represents a float or int encoded for Paillier encryption.
7+
8+
For end users, this class is mainly useful for specifying precision
9+
when adding/multiplying an :class:`EncryptedNumber` by a scalar.
10+
11+
If you want to manually encode a number for Paillier encryption,
12+
then use :meth:`encode`, if de-serializing then use
13+
:meth:`__init__`.
14+
15+
16+
.. note::
17+
If working with other Paillier libraries you will have to agree on
18+
a specific :attr:`BASE` and :attr:`LOG2_BASE` - inheriting from this
19+
class and overriding those two attributes will enable this.
20+
21+
Notes:
22+
Paillier encryption is only defined for non-negative integers less
23+
than :attr:`PaillierPublicKey.n`. Since we frequently want to use
24+
signed integers and/or floating point numbers (luxury!), values
25+
should be encoded as a valid integer before encryption.
26+
27+
The operations of addition and multiplication [1]_ must be
28+
preserved under this encoding. Namely:
29+
30+
1. Decode(Encode(a) + Encode(b)) = a + b
31+
2. Decode(Encode(a) * Encode(b)) = a * b
32+
33+
for any real numbers a and b.
34+
35+
Representing signed integers is relatively easy: we exploit the
36+
modular arithmetic properties of the Paillier scheme. We choose to
37+
represent only integers between
38+
+/-:attr:`~PaillierPublicKey.max_int`, where `max_int` is
39+
approximately :attr:`~PaillierPublicKey.n`/3 (larger integers may
40+
be treated as floats). The range of values between `max_int` and
41+
`n` - `max_int` is reserved for detecting overflows. This encoding
42+
scheme supports properties #1 and #2 above.
43+
44+
Representing floating point numbers as integers is a harder task.
45+
Here we use a variant of fixed-precision arithmetic. In fixed
46+
precision, you encode by multiplying every float by a large number
47+
(e.g. 1e6) and rounding the resulting product. You decode by
48+
dividing by that number. However, this encoding scheme does not
49+
satisfy property #2 above: upon every multiplication, you must
50+
divide by the large number. In a Paillier scheme, this is not
51+
possible to do without decrypting. For some tasks, this is
52+
acceptable or can be worked around, but for other tasks this can't
53+
be worked around.
54+
55+
In our scheme, the "large number" is allowed to vary, and we keep
56+
track of it. It is:
57+
58+
:attr:`BASE` ** :attr:`exponent`
59+
60+
One number has many possible encodings; this property can be used
61+
to mitigate the leak of information due to the fact that
62+
:attr:`exponent` is never encrypted.
63+
64+
For more details, see :meth:`encode`.
65+
66+
.. rubric:: Footnotes
67+
68+
.. [1] Technically, since Paillier encryption only supports
69+
multiplication by a scalar, it may be possible to define a
70+
secondary encoding scheme `Encode'` such that property #2 is
71+
relaxed to:
72+
73+
Decode(Encode(a) * Encode'(b)) = a * b
74+
75+
We don't do this.
76+
77+
78+
Args:
79+
public_key (PaillierPublicKey): public key for which to encode
80+
(this is necessary because :attr:`~PaillierPublicKey.max_int`
81+
varies)
82+
encoding (int): The encoded number to store. Must be positive and
83+
less than :attr:`~PaillierPublicKey.max_int`.
84+
exponent (int): Together with :attr:`BASE`, determines the level
85+
of fixed-precision used in encoding the number.
86+
87+
Attributes:
88+
public_key (PaillierPublicKey): public key for which to encode
89+
(this is necessary because :attr:`~PaillierPublicKey.max_int`
90+
varies)
91+
encoding (int): The encoded number to store. Must be positive and
92+
less than :attr:`~PaillierPublicKey.max_int`.
93+
exponent (int): Together with :attr:`BASE`, determines the level
94+
of fixed-precision used in encoding the number.
95+
"""
96+
BASE = 16
97+
"""Base to use when exponentiating. Larger `BASE` means
98+
that :attr:`exponent` leaks less information. If you vary this,
99+
you'll have to manually inform anyone decoding your numbers.
100+
"""
101+
LOG2_BASE = math.log(BASE, 2)
102+
FLOAT_MANTISSA_BITS = sys.float_info.mant_dig
103+
104+
def __init__(self, public_key, encoding, exponent):
105+
self.public_key = public_key
106+
self.encoding = encoding
107+
self.exponent = exponent
108+
109+
@classmethod
110+
def encode(cls, public_key, scalar, precision=None, max_exponent=None):
111+
"""Return an encoding of an int or float.
112+
113+
This encoding is carefully chosen so that it supports the same
114+
operations as the Paillier cryptosystem.
115+
116+
If *scalar* is a float, first approximate it as an int, `int_rep`:
117+
118+
scalar = int_rep * (:attr:`BASE` ** :attr:`exponent`),
119+
120+
for some (typically negative) integer exponent, which can be
121+
tuned using *precision* and *max_exponent*. Specifically,
122+
:attr:`exponent` is chosen to be equal to or less than
123+
*max_exponent*, and such that the number *precision* is not
124+
rounded to zero.
125+
126+
Having found an integer representation for the float (or having
127+
been given an int `scalar`), we then represent this integer as
128+
a non-negative integer < :attr:`~PaillierPublicKey.n`.
129+
130+
Paillier homomorphic arithemetic works modulo
131+
:attr:`~PaillierPublicKey.n`. We take the convention that a
132+
number x < n/3 is positive, and that a number x > 2n/3 is
133+
negative. The range n/3 < x < 2n/3 allows for overflow
134+
detection.
135+
136+
Args:
137+
public_key (PaillierPublicKey): public key for which to encode
138+
(this is necessary because :attr:`~PaillierPublicKey.n`
139+
varies).
140+
scalar: an int or float to be encrypted.
141+
If int, it must satisfy abs(*value*) <
142+
:attr:`~PaillierPublicKey.n`/3.
143+
If float, it must satisfy abs(*value* / *precision*) <<
144+
:attr:`~PaillierPublicKey.n`/3
145+
(i.e. if a float is near the limit then detectable
146+
overflow may still occur)
147+
precision (float): Choose exponent (i.e. fix the precision) so
148+
that this number is distinguishable from zero. If `scalar`
149+
is a float, then this is set so that minimal precision is
150+
lost. Lower precision leads to smaller encodings, which
151+
might yield faster computation.
152+
max_exponent (int): Ensure that the exponent of the returned
153+
`EncryptedNumber` is at most this.
154+
155+
Returns:
156+
EncodedNumber: Encoded form of *scalar*, ready for encryption
157+
against *public_key*.
158+
"""
159+
# Calculate the maximum exponent for desired precision
160+
if precision is None:
161+
if isinstance(scalar, int):
162+
prec_exponent = 0
163+
elif isinstance(scalar, float):
164+
# Encode with *at least* as much precision as the python float
165+
# What's the base-2 exponent on the float?
166+
bin_flt_exponent = math.frexp(scalar)[1]
167+
168+
# What's the base-2 exponent of the least significant bit?
169+
# The least significant bit has value 2 ** bin_lsb_exponent
170+
bin_lsb_exponent = bin_flt_exponent - cls.FLOAT_MANTISSA_BITS
171+
172+
# What's the corresponding base BASE exponent? Round that down.
173+
prec_exponent = math.floor(bin_lsb_exponent / cls.LOG2_BASE)
174+
else:
175+
raise TypeError("Don't know the precision of type %s."
176+
% type(scalar))
177+
else:
178+
prec_exponent = math.floor(math.log(precision, cls.BASE))
179+
180+
# Remember exponents are negative for numbers < 1.
181+
# If we're going to store numbers with a more negative
182+
# exponent than demanded by the precision, then we may
183+
# as well bump up the actual precision.
184+
if max_exponent is None:
185+
exponent = prec_exponent
186+
else:
187+
exponent = min(max_exponent, prec_exponent)
188+
189+
int_rep = int(round(scalar * pow(cls.BASE, -exponent)))
190+
191+
if abs(int_rep) > public_key.max_int:
192+
raise ValueError('Integer needs to be within +/- %d but got %d'
193+
% (public_key.max_int, int_rep))
194+
195+
# Wrap negative numbers by adding n
196+
return cls(public_key, int_rep % public_key.n, exponent)
197+
198+
def decode(self):
199+
"""Decode plaintext and return the result.
200+
201+
Returns:
202+
an int or float: the decoded number. N.B. if the number
203+
returned is an integer, it will not be of type float.
204+
205+
Raises:
206+
OverflowError: if overflow is detected in the decrypted number.
207+
"""
208+
if self.encoding >= self.public_key.n:
209+
# Should be mod n
210+
raise ValueError('Attempted to decode corrupted number')
211+
elif self.encoding <= self.public_key.max_int:
212+
# Positive
213+
mantissa = self.encoding
214+
elif self.encoding >= self.public_key.n - self.public_key.max_int:
215+
# Negative
216+
mantissa = self.encoding - self.public_key.n
217+
else:
218+
raise OverflowError('Overflow detected in decrypted number')
219+
220+
return mantissa * pow(self.BASE, self.exponent)
221+
222+
def decrease_exponent_to(self, new_exp):
223+
"""Return an `EncodedNumber` with same value but lower exponent.
224+
225+
If we multiply the encoded value by :attr:`BASE` and decrement
226+
:attr:`exponent`, then the decoded value does not change. Thus
227+
we can almost arbitrarily ratchet down the exponent of an
228+
:class:`EncodedNumber` - we only run into trouble when the encoded
229+
integer overflows. There may not be a warning if this happens.
230+
231+
This is necessary when adding :class:`EncodedNumber` instances,
232+
and can also be useful to hide information about the precision
233+
of numbers - e.g. a protocol can fix the exponent of all
234+
transmitted :class:`EncodedNumber` to some lower bound(s).
235+
236+
Args:
237+
new_exp (int): the desired exponent.
238+
239+
Returns:
240+
EncodedNumber: Instance with the same value and desired
241+
exponent.
242+
243+
Raises:
244+
ValueError: You tried to increase the exponent, which can't be
245+
done without decryption.
246+
"""
247+
if new_exp > self.exponent:
248+
raise ValueError('New exponent %i should be more negative than'
249+
'old exponent %i' % (new_exp, self.exponent))
250+
factor = pow(self.BASE, self.exponent - new_exp)
251+
new_enc = self.encoding * factor % self.public_key.n
252+
return self.__class__(self.public_key, new_enc, new_exp)

0 commit comments

Comments
 (0)