lhe module

Python homomorphic encryption library supporting up to three multiplications and unlimited additions.

class lhe.lhe.CTG1(g1r: G1, g1m_pr: G1)[source]

Bases: tuple

Ciphertext in strictly G1 x G1 only.

g1r: mclbn256.mclbn256.G1

Alias for field number 0

g1m_pr: mclbn256.mclbn256.G1

Alias for field number 1

class lhe.lhe.CTG2(g2r: G2, g2m_pr: G2)[source]

Bases: tuple

Ciphertext in strictly G2 x G2 only.

g2r: mclbn256.mclbn256.G2

Alias for field number 0

g2m_pr: mclbn256.mclbn256.G2

Alias for field number 1

class lhe.lhe.CTGT(z_r1_r2: GT, z_m2_s2_r2__r1: GT, z_m1_s1_r1__r2: GT, z_m1_s1_r1__m2_s2_r2: GT)[source]

Bases: tuple

Level-2 ciphertext in $ extsf{GT}^{4}$.

z_r1_r2: mclbn256.mclbn256.GT

Alias for field number 0

z_m2_s2_r2__r1: mclbn256.mclbn256.GT

Alias for field number 1

z_m1_s1_r1__r2: mclbn256.mclbn256.GT

Alias for field number 2

z_m1_s1_r1__m2_s2_r2: mclbn256.mclbn256.GT

Alias for field number 3

class lhe.lhe.CT1(ctg1: CTG1, ctg2: CTG2)[source]

Bases: tuple

All-purpose (dual) level-1 ciphertext making use of both G1 and G2.

ctg1: lhe.lhe.CTG1

Alias for field number 0

ctg2: lhe.lhe.CTG2

Alias for field number 1

class lhe.lhe.CT2(ctgt: CTGT)[source]

Bases: tuple

Level-2 ciphertext (wrapper around GT^4).

ctgt: lhe.lhe.CTGT

Alias for field number 0

class lhe.lhe.KPG1(sk: Fr, pk: G1)[source]

Bases: tuple

Keypair for G1-based encryption.

sk: mclbn256.mclbn256.Fr

Alias for field number 0

pk: mclbn256.mclbn256.G1

Alias for field number 1

class lhe.lhe.KPG2(sk: Fr, pk: G2)[source]

Bases: tuple

Keypair for G2-based encryption.

sk: mclbn256.mclbn256.Fr

Alias for field number 0

pk: mclbn256.mclbn256.G2

Alias for field number 1

class lhe.lhe.SK(s1: Fr, s2: Fr)[source]

Bases: tuple

Dual secret key for decryption

s1: mclbn256.mclbn256.Fr

Alias for field number 0

s2: mclbn256.mclbn256.Fr

Alias for field number 1

class lhe.lhe.PK(p1: G1, p2: G2)[source]

Bases: tuple

Dual public key for encryption (either ‘dumb’ group-agnostic, or optimal)

p1: mclbn256.mclbn256.G1

Alias for field number 0

p2: mclbn256.mclbn256.G2

Alias for field number 1

lhe.lhe.keygen_G1() Tuple[mclbn256.mclbn256.Fr, mclbn256.mclbn256.G1][source]

Generate a G1 keypair.

lhe.lhe.keygen_G2() lhe.lhe.KPG2[source]

Generate a G2 keypair.

lhe.lhe.keygen() Tuple[lhe.lhe.SK, lhe.lhe.PK][source]

Generate a dual keypair.

lhe.lhe.encrypt_G1(p: mclbn256.mclbn256.G1, m: int) lhe.lhe.CTG1[source]

Encrypt a plaintext to be a G1 ciphertext.

lhe.lhe.encrypt_G2(p: mclbn256.mclbn256.G2, m: int) lhe.lhe.CTG2[source]

Encrypt a plaintext to be a G2 ciphertext.

lhe.lhe.encrypt_GT(p1: mclbn256.mclbn256.G1, p2: mclbn256.mclbn256.G2, m: int) lhe.lhe.CTGT[source]

Encrypt a plaintext to be a GT ciphertext.

Each such ciphertext is made of the components z1 ** (-t + r + s) z1 ** (r * s2) z1 ** (s * s1) z1 ** (m + (t * s1 * s2)) from which (given the secrets, s1 and s2) you can compute z1 ** s and z1 ** r, then subtract from the first to get z1 ** -t which can then yield z1 ** (-t * s1 * s2), the inverse of everything in z1 ** (m + (t * s1 * s2)) except z1 ** m. m is finally extracted by a d.log. computation.

lhe.lhe.encrypt_lvl_1(pk: lhe.lhe.PK, m: int) lhe.lhe.CT1[source]

Encrypt a plaintext to be a dual (‘dumb’) ciphertext.

lhe.lhe.encrypt_lvl_2(pk: lhe.lhe.PK, m: int) lhe.lhe.CT2[source]

Encrypt a level-2 ciphertext.

lhe.lhe.add_G1(ct1: lhe.lhe.CTG1, ct2: lhe.lhe.CTG1) lhe.lhe.CTG1[source]

Homomorphically add two G1 ciphertexts in level 1.

lhe.lhe.add_G2(ct1: lhe.lhe.CTG2, ct2: lhe.lhe.CTG2) lhe.lhe.CTG2[source]

Homomorphically add two G2 ciphertexts in level 1.

lhe.lhe.add_GT(ct1: lhe.lhe.CTGT, ct2: lhe.lhe.CTGT) lhe.lhe.CTGT[source]

Homomorphically add two GT ciphertexts in level 2.

lhe.lhe.multiply_G1_G2(ct1: lhe.lhe.CTG1, ct2: lhe.lhe.CTG2) lhe.lhe.CTGT[source]

Homomorphically multiply two complementary level-1 ciphertexts and return a level-2 ciphertext of their product.

lhe.lhe.decrypt_G1(s1: mclbn256.mclbn256.Fr, ct: lhe.lhe.CTG1) Optional[int][source]

Decrypt a G1 ciphertext to a plaintext.

>>> sk, pk = keygen_G1()
>>> ct = encrypt_G1(pk, 737)
>>> print(decrypt_G1(sk, ct))
737
lhe.lhe.decrypt_G2(s2: mclbn256.mclbn256.Fr, ct: lhe.lhe.CTG2) Optional[int][source]

Decrypt a G2 ciphertext to a plaintext.

>>> sk, pk = keygen_G2()
>>> ct = encrypt_G2(pk, 747)
>>> int(decrypt_G2(sk, ct))
747
lhe.lhe.decrypt_GT(s1: mclbn256.mclbn256.Fr, s2: mclbn256.mclbn256.Fr, ct: lhe.lhe.CTGT)[source]

Decrypt a level-2 ciphertext.

>>> sk1, pk1 = keygen_G1()
>>> sk2, pk2 = keygen_G2()
>>> ct11 = encrypt_G1(pk1, 1)
>>> ct12 = encrypt_G1(pk1, 2)
>>> ct21 = encrypt_G2(pk2, 200)
>>> ct22 = encrypt_G2(pk2, 22)
>>> ct1 = ct11 + ct12
>>> ct2 = ct21 + ct22
>>> ct3 = ct1 * ct2
>>> pt = decrypt_GT(sk1, sk2, ct3)
>>> int(pt)
666
>>> sk, pk = keygen()
>>> ct_1 = encrypt_lvl_1(pk, 1)
>>> ct_2 = encrypt_lvl_1(pk, 2)
>>> ct_200 = encrypt_lvl_1(pk, 200)
>>> ct_22 = encrypt_lvl_1(pk, 22)
>>> ct_3 = ct_1 + ct_2
>>> ct_222 = ct_200 + ct_22
>>> ct_666 = ct_3 * ct_222
>>> pt = decrypt(sk, ct_666)
>>> int(pt)
666

The goal is to unmask the last ciphertext component and get z1 ** (m1 * m2).

Note that that component, z1 ** ((m1 + (s1 * r1)) * (m2 + (s2 * r2))), expands to equal

= z1 ** (m2 * r1 * s1) * z1 ** (m1 * r2 * s2) * z1 ** (r1 * r2 * s1 * s2) * z1 ** (m1 * m2) for whose terms we already have the ingredients to construct.

The z1 ** (r1 * r2 * s1 * s2) specifically cancels the last negative term in the ct.z_m2_s2_r2__r1 by ct.z_m1_s1_r1__r2 product.

We have z1 to the power of, (m2 + r1 s1)(m1 + r2 s2) = (m1 m2) + (m1 r1 s1) + (m2 r2 s2) + (r1 r2 s1 s2).

And z1 to the power of, (r1 r2)(s1 s2) + r1 (m1 + r2 s2)(-s1) + r2 (m2 + r1 s1)(-s2) = -(m1 r1 s1) + -(m2 r2 s2) + -(r1 r2 s2 s1).

Thus, we may decrypt by add these exponents (by multiplying powers) to get m1*m2 which can be extracted by a discrete log.

lhe.lhe.decrypt(sk: lhe.lhe.SK, ct: Union[lhe.lhe.CT1, lhe.lhe.CT2, lhe.lhe.CTG1, lhe.lhe.CTG2, lhe.lhe.CTGT]) mclbn256.mclbn256.Fr[source]

Type-generic decryption helper

>>> sk, pk = keygen()
>>> pt_m = Fr() % (2 ** 12)
>>> m = int(pt_m)
>>> 0 <= m < 2 ** 12
True
>>> decrypt(sk, encrypt_G1(pk.p1, m)) == pt_m
True
>>> decrypt(sk, encrypt_G2(pk.p2, m)) == pt_m
True
>>> decrypt(sk, encrypt_GT(pk.p1, pk.p2, m)) == pt_m
True
>>> decrypt(sk, encrypt_lvl_1(pk, m)) == pt_m
True
>>> decrypt(sk, encrypt_lvl_2(pk, m)) == pt_m
True
lhe.lhe.dlog(base: Union[mclbn256.mclbn256.Fr, mclbn256.mclbn256.G1, mclbn256.mclbn256.G2, mclbn256.mclbn256.GT], power: mclbn256.mclbn256.GT, unsigned=False) Optional[mclbn256.mclbn256.Fr][source]

Discrete logarithm on any group, either Fr, G1, G2, or GT.

Can work with up to 20-bits before giving up. The example below tests 16-bit exponents of each type (for efficiency).

This helper may be replaced with Pollard’s Kangaroo method for a big boost (~2x) in performance. That optimization is unimplemented. Alternatively, we may use a lookup table.

>>> x = Fr()
>>> a = Fr() % (2 ** 16)
>>> dlog(x, x ** a) == a
True
>>> x = G1().randomize()
>>> a = Fr() % (2 ** 16)
>>> y = x * a
>>> dlog(x, y) == a
True
>>> x = G2().randomize()
>>> a = Fr() % (2 ** 16)
>>> y = x * a
>>> dlog(x, y) == a
True
>>> x = G1().randomize() @ G2().randomize()
>>> a = Fr() % (2 ** 16)
>>> y = x ** a
>>> dlog(x, y) == a
True
lhe.lhe.main()[source]
lhe.lhe.encrypt(pk: lhe.lhe.PK, m: int) lhe.lhe.CT1

Encrypt a plaintext to be a dual (‘dumb’) ciphertext.