Source code for gabes.gate

"""
This module implements the :class:`Gate` object. The bulk of **gabes**
resides on this module. In it, both garbling and ungarbling (or evaluating)
techniques are implemented.

"""

import os
import pickle
import gabes.settings as settings
import hashlib

from gabes.wire import Wire
from gabes.label import Label
from gabes.utils import get_last_bit, xor, adjust_wire_offset
from gabes.crypto import AESKey, generate_zero_ciphertext
from cryptography.fernet import Fernet, InvalidToken
from Crypto.Random.random import shuffle


[docs]class Gate(object): """ The :class:`Gate` object contains three wires: a left wire, a right wire, and an output wire, each having a false label and a true label. Depending on the settings, different optimizations will be used to garble and ungarble. :param str gate_type: type of gate (AND, OR, etc) :param bool create_left: whether to create the left wire \ on the gate's initialization :param bool create_right: whether to create the right wire \ on the gate's initialization """ gates = { 'AND': lambda in1, in2: in1 & in2, 'XOR': lambda in1, in2: in1 ^ in2, 'OR': lambda in1, in2: in1 | in2 } def __init__(self, gate_type, create_left=True, create_right=True): self.table = [] self.gate_type = gate_type self.left_wire = Wire() if create_left else None self.right_wire = Wire() if create_right else None self.output_wire = Wire() def __str__(self): return self.gate_type
[docs] def garble(self): """ Garbles the gate. Delegates to the correct optimization depending on the user's choice. """ if settings.CLASSICAL: self.classical_garble() elif settings.POINT_AND_PERMUTE: self.point_and_permute_garble() elif settings.FREE_XOR: self.free_xor_garble() elif settings.FLEXOR: self.flexor_garble() elif settings.GRR3: self.grr3_garble() elif settings.HALF_GATES: self.half_gates_garble()
[docs] def classical_garble(self): """ The most simple type of garbling. In classical garbled circuits, the whole boolean table is obfuscated by encrypting the output label using the input labels as keys. After this the table is shuffled (or *garbled*) so that the evaluator can't know more than one output label. For more information see `the paper <https://dl.acm.org/citation.cfm?id=1382944>`_. Note that a *Fernet* scheme is used since this method relies on knowing whether decryption was successful or not, as the evaluator needs to try and decrypt the four possible entries in the boolean table. """ for left_label in self.left_wire.labels(): for right_label in self.right_wire.labels(): key1 = Fernet(left_label.to_base64()) key2 = Fernet(right_label.to_base64()) in1, in2 = left_label.represents, right_label.represents logic_value = self.evaluate_gate(in1, in2) output_label = self.output_wire.get_label(logic_value) pickled = pickle.dumps(output_label) table_entry = key1.encrypt(key2.encrypt(pickled)) self.table.append(table_entry) shuffle(self.table)
[docs] def point_and_permute_garble(self): """ In this optimization each label has a point-and-permute bit associated to it, with the only rule that labels running in the same wire must have opposing point-and-permute bits. The garbler will insert the encrypted output labels according to the point-and-permute bit of the input labels. Therefore, now the evaluator does not need to try and decrypt all the four ciphers but rather the one indicated by the two point-and-permute bits he has. For more information see `the paper <https://pdfs.semanticscholar.org/f71d/b4d70d4cc9e931a6\ 3dde7a6db8dad10a61a0.pdf>`_. """ self.table = [None] * 4 for left_label in self.left_wire.labels(): for right_label in self.right_wire.labels(): key1 = AESKey(left_label.to_base64()) key2 = AESKey(right_label.to_base64()) in1, in2 = left_label.represents, right_label.represents logic_value = self.evaluate_gate(in1, in2) output_label = self.output_wire.get_label(logic_value) pickled = pickle.dumps(output_label) entry = key1.encrypt(key2.encrypt(pickled)) self.table[2 * left_label.pp_bit + right_label.pp_bit] = entry
[docs] def grr3_garble(self): """ In this optimization the entry corresponding to the two labels that have a false point-and-permute bit is not sent over the network. Instead, the output label corresponding to this entry is set to be equal to the decryption of the zero ciphertext. Therefore, there is no need to send the entry because it is simply the zero ciphertext. The only thing the evaluator needs to do is to conclude that if he receives two false point-and-permute bits, the ciphertext will be the all zeros. For more information see `the paper <http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.2\ 4.6692&rep=rep1&type=pdf>`_. Note that now *Fernet* schemes can not be used since there is no way to decrypt the zero ciphertext. Instead, AES is used. See the Cryptography section for more details. """ self.set_zero_ciphertext() self.table = [None] * 3 for left_label in self.left_wire.labels(): for right_label in self.right_wire.labels(): left_pp_bit = left_label.pp_bit right_pp_bit = right_label.pp_bit if left_pp_bit or right_pp_bit: key1 = AESKey(left_label.to_base64()) key2 = AESKey(right_label.to_base64()) in1, in2 = left_label.represents, right_label.represents logic_value = self.evaluate_gate(in1, in2) output_label = self.output_wire.get_label(logic_value) pickled = pickle.dumps(output_label) entry = key1.encrypt(key2.encrypt(pickled), to_base64=True) self.table[2 * left_pp_bit + right_pp_bit - 1] = entry
[docs] def free_xor_garble(self): """ In this optimization *XOR* gates are garbled for free, that is, the table corresponding to this gate is empty. The way this optimization accomplishes this is by setting the true label of each wire as an offset *R* of the false label. This offset is global to the whole circuit, so by the properties of *XOR*, everything works out nicely. For more information see `the paper <http://www.cs.toronto.edu/~vlad/papers/XOR_ICALP08.pdf>`_. Note that FreeXOR is not compatible with GRR2. """ if self.gate_type == 'XOR': A0 = self.left_wire.false_label.label B0 = self.right_wire.false_label.label R = settings.R C0, C1 = xor(A0, B0), xor(xor(A0, B0), R) pp_bit = get_last_bit(C0) f_label = self.output_wire.false_label t_label = self.output_wire.true_label f_label.label = C0 f_label.pp_bit = pp_bit t_label.label = C1 t_label.pp_bit = not pp_bit else: if settings.GRR3: self.grr3_garble() else: self.point_and_permute_garble()
[docs] def flexor_garble(self): """ In this optimization *XOR* are garbled with a table size of 0, 1, or 2 (hence its name flexible XORs). The innovation at the time was that this method is compatible with GRR2. The way it accomplishes this is by changing the input wires' labels to have the same offset as the output wire's labels. For more information see `the paper <https://pdfs.semanticscholar.org/72ba/7c639e3d7b07\ 5fde8eeca3385923551c6a39.pdf>`_. """ if self.gate_type == 'XOR': self.table = [None] * 4 left, right, out = self.wires() adjust_wire_offset(out) A0, A1 = left.false_label.label, left.true_label.label B0, B1 = right.false_label.label, right.true_label.label C0, C1 = out.false_label.label, out.true_label.label R1, R2, R3 = xor(A0, A1), xor(B0, B1), xor(C0, C1) k1, k2 = AESKey(A0), AESKey(B0) A0_ = k1.decrypt(bytes(settings.NUM_BYTES)) B0_ = k2.decrypt(bytes(settings.NUM_BYTES)) C0_, C1_ = xor(A0_, B0_), xor(xor(A0_, B0_), R3) A1_, B1_ = xor(A0_, R3), xor(B0_, R3) self.modify_pp_bits(A0_, B0_, C0_) out.false_label.label = C0_ out.true_label.label = C1_ k1, k2 = AESKey(A1), AESKey(B1) if R1 == R2 == R3: self.free_xor_garble() elif R1 != R2 != R3: self.table[left.true_label.pp_bit] = k1.encrypt(A1_) self.table[right.true_label.pp_bit + 2] = k2.encrypt(B1_) elif R1 == R3: self.table[right.true_label.pp_bit + 2] = k2.encrypt(B1_) elif R2 == R3: self.table[left.true_label.pp_bit] = k1.encrypt(A1_) else: if settings.GRR3: self.grr3_garble() else: self.point_and_permute_garble()
[docs] def half_gates_garble(self): """ In this optimization, the most current one to date, the authors propose a method to garble *AND* gates with a table size of two ciphertexts in a way that is compatible with FreeXOR. The way they accomplish this is by breaking up an *AND* gate into two *half gates*. For more information see `the paper <https://eprint.iacr.org/2014/756.pdf>`_. """ if self.gate_type == 'AND': def H(x): return hashlib.sha256(x).digest() self.table = [None] * 2 p_a = self.left_wire.false_label.pp_bit p_b = self.right_wire.false_label.pp_bit # Generator Half Gate entry1 = xor(H(self.left_wire.false_label.label), H(self.left_wire.true_label.label)) if p_b: entry1 = xor(entry1, settings.R) C0 = H(self.left_wire.false_label.label) if p_a: C0 = xor(C0, entry1) # Evaluator Half Gate entry2 = xor(H(self.right_wire.false_label.label), H(self.right_wire.true_label.label)) entry2 = xor(entry2, self.left_wire.false_label.label) C0_ = H(self.right_wire.false_label.label) if p_b: C0_ = xor(C0_, xor(entry2, self.left_wire.false_label.label)) self.table = [entry1, entry2] self.update_output_wire(xor(C0, C0_), xor(xor(C0, C0_), settings.R)) else: self.free_xor_garble()
[docs] def update_output_wire(self, false_label, true_label): """ Updates the output wire's labels and point-and-permute bits. :param false_label: the false label :param true_label: the true label """ self.output_wire.false_label.label = false_label self.output_wire.true_label.label = true_label self.output_wire.false_label.pp_bit = get_last_bit(false_label) self.output_wire.true_label.pp_bit = get_last_bit(true_label)
[docs] def set_zero_ciphertext(self): """ Generates the zero ciphertext by taking the two labels with false point-and-permute bits and setting the output labels accordingly. This function is used for GRR3. """ l, r = self.left_wire, self.right_wire pp_left = l.false_label if not l.false_label.pp_bit else l.true_label pp_right = r.false_label if not r.false_label.pp_bit else r.true_label output_label = generate_zero_ciphertext(pp_left, pp_right) in1, in2 = pp_left.represents, pp_right.represents logic_value = self.evaluate_gate(in1, in2) true_label = self.output_wire.true_label false_label = self.output_wire.false_label if logic_value: true_label.label = output_label pp_bit = get_last_bit(output_label) true_label.pp_bit = pp_bit false_label.pp_bit = not pp_bit else: false_label.label = output_label pp_bit = get_last_bit(output_label) false_label.pp_bit = pp_bit true_label.pp_bit = not pp_bit
[docs] def modify_pp_bits(self, A0_, B0_, C0_): """ Modifies the point-and-permute bits according to the last bit of the label. """ left, right, out = self.wires() l_pp_bit, r_pp_bit = get_last_bit(A0_), get_last_bit(B0_) out_pp_bit = get_last_bit(C0_) left.false_label.pp_bit = l_pp_bit left.true_label.pp_bit = not l_pp_bit right.false_label.pp_bit = r_pp_bit right.true_label.pp_bit = not r_pp_bit out.false_label.pp_bit = out_pp_bit out.true_label.pp_bit = not out_pp_bit
[docs] def ungarble(self, garblers_label, evaluators_label): """ Ungarbles the gate. Delegates to the correct optimization depending on the user's choice. :param garblers_label: the chosen label by the garbler :param evaluators_label: the chosen label by the evaluator :return: the correct output label :rtype: :class:`Label` """ g, e = garblers_label, evaluators_label if settings.CLASSICAL: output_label = self.classical_ungarble(g, e) elif settings.POINT_AND_PERMUTE: output_label = self.point_and_permute_ungarble(g, e) elif settings.FREE_XOR: output_label = self.free_xor_ungarble(g, e) elif settings.FLEXOR: output_label = self.flexor_ungarble(g, e) elif settings.GRR3: output_label = self.grr3_ungarble(g, e) elif settings.HALF_GATES: output_label = self.half_gates_ungarble(g, e) return output_label
[docs] def classical_ungarble(self, garblers_label, evaluators_label): """ The classical evaluation, in which the evaluator tries the four possible table entries until one of them decrypts the cipher. :param garblers_label: the chosen label by the garbler :param evaluators_label: the chosen label by the evaluator :return: the correct output label :rtype: :class:`Label` """ for table_entry in self.table: try: key1 = Fernet(garblers_label.to_base64()) key2 = Fernet(evaluators_label.to_base64()) output_label = pickle.loads(key2.decrypt( key1.decrypt(table_entry))) except InvalidToken: # Wrong table entry, try again pass return output_label
[docs] def point_and_permute_ungarble(self, garblers_label, evaluators_label): """ Evaluates the gate by indexing the table according to the point-and-permute bits given. :param garblers_label: the chosen label by the garbler :param evaluators_label: the chosen label by the evaluator :return: the correct output label :rtype: :class:`Label` """ left_pp_bit = garblers_label.pp_bit right_pp_bit = evaluators_label.pp_bit key1 = AESKey(garblers_label.to_base64()) key2 = AESKey(evaluators_label.to_base64()) table_entry = self.table[2 * left_pp_bit + right_pp_bit] output_label = pickle.loads(key2.decrypt(key1.decrypt(table_entry))) return output_label
[docs] def grr3_ungarble(self, garblers_label, evaluators_label): """ If the point-and-permute bits are false, then imagine the ciphertext was the all zero ciphertext. Otherwise, proceed as in the point-and-permute optimization. :param garblers_label: the chosen label by the garbler :param evaluators_label: the chosen label by the evaluator :return: the correct output label :rtype: :class:`Label` """ left_pp_bit = garblers_label.pp_bit right_pp_bit = evaluators_label.pp_bit key1 = AESKey(garblers_label.to_base64()) key2 = AESKey(evaluators_label.to_base64()) if not left_pp_bit and not right_pp_bit: zero_ciphertext = bytes(settings.NUM_BYTES) output_label = Label(0) output_label.represents = None output_label.label = key2.decrypt(key1.decrypt(zero_ciphertext, unpad=False), unpad=False) output_label.pp_bit = get_last_bit(output_label.label) else: table_entry = self.table[2 * left_pp_bit + right_pp_bit - 1] output_label = pickle.loads(key2.decrypt( key1.decrypt(table_entry, from_base64=True))) return output_label
[docs] def free_xor_ungarble(self, garblers_label, evaluators_label): """ Evaluates *XOR* gates for free by XORing the two labels he receives. :param garblers_label: the chosen label by the garbler :param evaluators_label: the chosen label by the evaluator :return: the correct output label :rtype: :class:`Label` """ if self.gate_type == 'XOR': output_label = Label(0) output_label.represents = None output_label.label = xor(garblers_label.label, evaluators_label.label) output_label.pp_bit = get_last_bit(output_label.label) else: g, e = garblers_label, evaluators_label if settings.GRR3: output_label = self.grr3_ungarble(g, e) else: output_label = self.point_and_permute_ungarble(g, e) return output_label
[docs] def flexor_ungarble(self, garblers_label, evaluators_label): """ Transforms the two input labels to have the same offset as the output's true label. :param garblers_label: the chosen label by the garbler :param evaluators_label: the chosen label by the evaluator :return: the correct output label :rtype: :class:`Label` """ if self.gate_type == 'XOR': garblers_label = self.transform_label(garblers_label, garbler=True) evaluators_label = self.transform_label(evaluators_label, garbler=False) output_label = self.free_xor_ungarble(garblers_label, evaluators_label) else: g, e = garblers_label, evaluators_label if settings.GRR3: output_label = self.grr3_ungarble(g, e) else: output_label = self.point_and_permute_ungarble(g, e) return output_label
[docs] def half_gates_ungarble(self, garblers_label, evaluators_label): """ Evaluates the gate by decrypting each half gate and XORing the result. :param garblers_label: the chosen label by the garbler :param evaluators_label: the chosen label by the evaluator :return: the correct output label :rtype: :class:`Label` """ if self.gate_type == 'AND': s_a, s_b = garblers_label.pp_bit, evaluators_label.pp_bit entry1, entry2 = self.table gen = hashlib.sha256(garblers_label.label).digest() eva = hashlib.sha256(evaluators_label.label).digest() C_g = xor(gen, entry1) if s_a else gen C_e = xor(eva, xor(entry2, garblers_label.label)) if s_b else eva output_label = Label(0) output_label.represents = None output_label.label = xor(C_g, C_e) output_label.pp_bit = get_last_bit(output_label.label) return output_label else: return self.free_xor_ungarble(garblers_label, evaluators_label)
[docs] def transform_label(self, label, garbler=True): """ Transforms the label accordingly. :param label: the label to transform :param bool garbler: the type of label supplied """ pp_bit = label.pp_bit key = AESKey(label.label) table_entry = self.table[pp_bit if garbler else pp_bit + 2] if not table_entry: table_entry = bytes(settings.NUM_BYTES) label.label = key.decrypt(table_entry) return label
[docs] def evaluate_gate(self, input1, input2): """ Evaluates a gate given two inputs. :param bool input1: the first input :param bool input2: the second input :return: the output of the gate :rtype: bool """ g = self.gates[self.gate_type] return g(input1, input2)
[docs] def wires(self): """ Returns the three wires related to the gate. :return: the three wires :rtype: list(:class:`Wire`) """ return (self.left_wire, self.right_wire, self.output_wire)