Example #1
0
    def add_if_statement_to_circuit(self, ast: IfStatement):
        """Include private if statement in this circuit."""

        # Handle if branch
        with self._remapper.remap_scope():
            comment = CircComment(f'if ({ast.condition.code()})')
            self._phi.append(comment)
            cond = self._evaluate_private_expression(ast.condition)
            comment.text += f' [{cond.name}]'
            self._circ_trafo.visitBlock(ast.then_branch, cond, True)
            then_remap = self._remapper.get_state()

            # Bubble up nested pre statements
            ast.pre_statements += ast.then_branch.pre_statements
            ast.then_branch.pre_statements = []

        # Handle else branch
        if ast.else_branch is not None:
            self._phi.append(CircComment(f'else [{cond.name}]'))
            self._circ_trafo.visitBlock(ast.else_branch, cond, False)

            # Bubble up nested pre statements
            ast.pre_statements += ast.else_branch.pre_statements
            ast.else_branch.pre_statements = []

        # SSA join branches (if both branches write to same external value -> cond assignment to select correct version)
        with self.circ_indent_block(f'JOIN [{cond.name}]'):
            cond_idf_expr = cond.get_idf_expr(ast)
            assert isinstance(cond_idf_expr, IdentifierExpr)
            self._remapper.join_branch(ast, cond_idf_expr, then_remap,
                                       self._create_temp_var)
Example #2
0
    def _ensure_encryption(self, stmt: Statement, plain: HybridArgumentIdf,
                           new_privacy: PrivacyLabelExpr,
                           crypto_params: CryptoParams,
                           cipher: HybridArgumentIdf, is_param: bool,
                           is_dec: bool):
        """
        Make sure that cipher = enc(plain, getPk(new_privacy), priv_user_provided_rnd).

        This automatically requests necessary keys and adds a circuit input for the randomness.

        Note: This function adds pre-statements to stmt

        :param stmt [SIDE EFFECT]: the statement which contains the expression which requires this encryption
        :param plain: circuit variable referencing the plaintext value
        :param new_privacy: privacy label corresponding to the destination key address
        :param cipher: circuit variable referencing the encrypted value
        :param is_param: whether cipher is a function parameter
        :param is_dec: whether this is a decryption operation (user supplied plain) as opposed to an encryption operation (user supplied cipher)
        """
        if crypto_params.is_symmetric_cipher():
            # Need a different set of keys for hybrid-encryption (ecdh-based) backends
            self._require_secret_key(crypto_params)
            my_pk = self._require_public_key_for_label_at(
                stmt, Expression.me_expr(), crypto_params)
            if is_dec:
                other_pk = self._get_public_key_in_sender_field(
                    stmt, cipher, crypto_params)
            else:
                if new_privacy == Expression.me_expr():
                    other_pk = my_pk
                else:
                    other_pk = self._require_public_key_for_label_at(
                        stmt, new_privacy, crypto_params)

                self.phi.append(
                    CircComment(
                        f'{cipher.name} = enc({plain.name}, ecdh({other_pk.name}, my_sk))'
                    ))
            self._phi.append(
                CircSymmEncConstraint(plain, other_pk, cipher, is_dec))
        else:
            rnd = self._secret_input_name_factory.add_idf(
                f'{plain.name if is_param else cipher.name}_R',
                TypeName.rnd_type(crypto_params))
            pk = self._require_public_key_for_label_at(stmt, new_privacy,
                                                       crypto_params)
            if not is_dec:
                self.phi.append(
                    CircComment(
                        f'{cipher.name} = enc({plain.name}, {pk.name})'))
            self._phi.append(CircEncConstraint(plain, rnd, pk, cipher, is_dec))
Example #3
0
 def add_block_to_circuit(self, ast: Block,
                          guard_cond: Optional[HybridArgumentIdf],
                          guard_val: Optional[bool]):
     assert ast.parent is not None
     is_already_scoped = isinstance(
         ast.parent, (ConstructorOrFunctionDefinition, IfStatement))
     self.phi.append(CircComment('{'))
     with self.circ_indent_block():
         with nullcontext() if guard_cond is None else self.guarded(
                 guard_cond, guard_val):
             with nullcontext(
             ) if is_already_scoped else self._remapper.remap_scope(ast):
                 for stmt in ast.statements:
                     self._circ_trafo.visit(stmt)
                     # Bubble up nested pre statements
                     ast.pre_statements += stmt.pre_statements
                     stmt.pre_statements = []
     self.phi.append(CircComment('}'))
Example #4
0
    def add_return_stmt_to_circuit(self, ast: ReturnStatement):
        self.phi.append(CircComment(ast.code()))
        assert ast.expr is not None
        if not isinstance(ast.expr, TupleExpr):
            ast.expr = TupleExpr([ast.expr])

        for vd, expr in zip(ast.function.return_var_decls, ast.expr.elements):
            # Assign return value to new version of return variable
            self.create_new_idf_version_from_value(vd.idf, expr)
Example #5
0
 def add_var_decl_to_circuit(self, ast: VariableDeclarationStatement):
     self.phi.append(CircComment(ast.code()))
     if ast.expr is None:
         # Default initialization is made explicit for circuit variables
         t = ast.variable_declaration.annotated_type.type_name
         assert t.can_be_private()
         ast.expr = TypeCheckVisitor.implicitly_converted_to(
             NumberLiteralExpr(0).override(parent=ast, statement=ast),
             t.clone())
     self.create_new_idf_version_from_value(ast.variable_declaration.idf,
                                            ast.expr)
Example #6
0
    def inline_function_call_into_circuit(
            self, fcall: FunctionCallExpr) -> Union[Expression, TupleExpr]:
        """
        Inline an entire function call into the current circuit.

        :param fcall: Function call to inline
        :return: Expression (1 retval) / TupleExpr (multiple retvals) with return value(s)
        """
        assert isinstance(fcall.func,
                          LocationExpr) and fcall.func.target is not None
        fdef = fcall.func.target
        with self._remapper.remap_scope(fcall.func.target.body):
            with nullcontext(
            ) if fcall.func.target.idf.name == '<stmt_fct>' else self.circ_indent_block(
                    f'INLINED {fcall.code()}'):
                # Assign all arguments to temporary circuit variables which are designated as the current version of the parameter idfs
                for param, arg in zip(fdef.parameters, fcall.args):
                    self.phi.append(
                        CircComment(f'ARG {param.idf.name}: {arg.code()}'))
                    with self.circ_indent_block():
                        self.create_new_idf_version_from_value(param.idf, arg)

                # Visit the untransformed target function body to include all statements in this circuit
                inlined_body = deep_copy(fdef.original_body,
                                         with_types=True,
                                         with_analysis=True)
                self._circ_trafo.visit(inlined_body)
                fcall.statement.pre_statements += inlined_body.pre_statements

                # Create TupleExpr with location expressions corresponding to the function return values as elements
                ret_idfs = [
                    self._remapper.get_current(vd.idf)
                    for vd in fdef.return_var_decls
                ]
                ret = TupleExpr([
                    IdentifierExpr(idf.clone()).as_type(idf.t)
                    for idf in ret_idfs
                ])
        if len(ret.elements) == 1:
            # Unpack 1-length tuple
            ret = ret.elements[0]
        return ret
Example #7
0
 def add_assignment_to_circuit(self, ast: AssignmentStatement):
     """Include private assignment statement in this circuit."""
     self.phi.append(CircComment(ast.code()))
     self._add_assign(ast.lhs, ast.rhs)
Example #8
0
    def add_to_circuit_inputs(self, expr: Expression) -> HybridArgumentIdf:
        """
        Add the provided expression to the public circuit inputs.

        Roughly corresponds to in() from paper

        If expr is encrypted (privacy != @all), this function also automatically ensures that the circuit has access to
        the correctly decrypted expression value in the form of a new private circuit input.

        If expr is an IdentifierExpr, its value will be cached
        (i.e. when the same identifier is needed again as a circuit input, its value will be retrieved from cache rather \
         than adding an expensive redundant input. The cache is invalidated as soon as the identifier is overwritten in public code)

        Note: This function has side effects on expr.statement (adds a pre_statement)

        :param expr: [SIDE EFFECT] expression which should be made available inside the circuit as an argument
        :return: HybridArgumentIdf which references the plaintext value of the newly added input
        """
        privacy = expr.annotated_type.privacy_annotation.privacy_annotation_label(
        ) if expr.annotated_type.is_private() else Expression.all_expr()
        is_public = privacy == Expression.all_expr()

        expr_text = expr.code()
        input_expr = self._expr_trafo.visit(expr)
        t = input_expr.annotated_type.type_name
        locally_decrypted_idf = None

        # If expression has literal type -> evaluate it inside the circuit (constant folding will be used)
        # rather than introducing an unnecessary public circuit input (expensive)
        if isinstance(t, BooleanLiteralType):
            return self._evaluate_private_expression(input_expr, str(t.value))
        elif isinstance(t, NumberLiteralType):
            return self._evaluate_private_expression(input_expr, str(t.value))

        t_suffix = ''
        if isinstance(expr, IdentifierExpr):
            # Look in cache before doing expensive move-in
            if self._remapper.is_remapped(expr.target.idf):
                remapped_idf = self._remapper.get_current(expr.target.idf)
                return remapped_idf

            t_suffix = f'_{expr.idf.name}'

        # Generate circuit inputs
        if is_public:
            tname = f'{self._in_name_factory.get_new_name(expr.annotated_type.type_name)}{t_suffix}'
            return_idf = input_idf = self._in_name_factory.add_idf(
                tname, expr.annotated_type.type_name)
            self._phi.append(CircComment(f'{input_idf.name} = {expr_text}'))
        else:
            # Encrypted inputs need to be decrypted inside the circuit (i.e. add plain as private input and prove encryption)
            tname = f'{self._secret_input_name_factory.get_new_name(expr.annotated_type.type_name)}{t_suffix}'
            return_idf = locally_decrypted_idf = self._secret_input_name_factory.add_idf(
                tname, expr.annotated_type.type_name)
            cipher_t = TypeName.cipher_type(input_expr.annotated_type,
                                            expr.annotated_type.homomorphism)
            tname = f'{self._in_name_factory.get_new_name(cipher_t)}{t_suffix}'
            input_idf = self._in_name_factory.add_idf(
                tname, cipher_t, IdentifierExpr(locally_decrypted_idf))

        # Add a CircuitInputStatement to the solidity code, which looks like a normal assignment statement,
        # but also signals the offchain simulator to perform decryption if necessary
        expr.statement.pre_statements.append(
            CircuitInputStatement(input_idf.get_loc_expr(), input_expr))

        if not is_public:
            # Check if the secret plain input corresponds to the decrypted cipher value
            crypto_params = cfg.get_crypto_params(
                expr.annotated_type.homomorphism)
            self._phi.append(
                CircComment(
                    f'{locally_decrypted_idf} = dec({expr_text}) [{input_idf.name}]'
                ))
            self._ensure_encryption(expr.statement, locally_decrypted_idf,
                                    Expression.me_expr(), crypto_params,
                                    input_idf, False, True)

        # Cache circuit input for later reuse if possible
        if cfg.opt_cache_circuit_inputs and isinstance(expr, IdentifierExpr):
            # TODO: What if a homomorphic variable gets used as both a plain variable and as a ciphertext?
            #       This works for now because we never perform homomorphic operations on variables we can decrypt.
            self._remapper.remap(expr.target.idf, return_idf)

        return return_idf