示例#1
0
    def try_rehom(rhs: Expression, expected_type: AnnotatedTypeName):
        if rhs.annotated_type.is_public():
            raise ValueError(
                'Cannot change the homomorphism of a public value')

        if rhs.annotated_type.is_private_at_me(rhs.analysis):
            # The value is @me, so we can just insert a ReclassifyExpr to change
            # the homomorphism of this value, just like we do for public values.
            return TypeCheckVisitor.make_rehom(rhs, expected_type)

        if isinstance(rhs, ReclassifyExpr) and not isinstance(rhs, RehomExpr):
            # rhs is a valid ReclassifyExpr, i.e. the argument is public or @me-private
            # To create an expression with the correct homomorphism,
            # just change the ReclassifyExpr's output homomorphism
            rhs.homomorphism = expected_type.homomorphism
        elif isinstance(rhs, PrimitiveCastExpr):
            # Ignore primitive cast & recurse
            rhs.expr = TypeCheckVisitor.try_rehom(rhs.expr, expected_type)
        elif isinstance(rhs, FunctionCallExpr) and isinstance(rhs.func, BuiltinFunction) and rhs.func.is_ite() \
                and rhs.args[0].annotated_type.is_public():
            # Argument is public_cond ? true_val : false_val. Try to rehom both true_val and false_val
            rhs.args[1] = TypeCheckVisitor.try_rehom(rhs.args[1],
                                                     expected_type)
            rhs.args[2] = TypeCheckVisitor.try_rehom(rhs.args[2],
                                                     expected_type)
        else:
            raise TypeMismatchException(expected_type, rhs.annotated_type, rhs)

        # Rehom worked without throwing, change annotated_type and return
        rhs.annotated_type = rhs.annotated_type.with_homomorphism(
            expected_type.homomorphism)
        return rhs
示例#2
0
 def make_private_if_not_already(self, ast: Expression):
     if ast.annotated_type.is_private():
         expected = AnnotatedTypeName(ast.annotated_type.type_name,
                                      Expression.me_expr())
         if not ast.instanceof(expected):
             raise TypeMismatchException(expected, ast.annotated_type, ast)
         return ast
     else:
         return self.make_private(ast, Expression.me_expr())
示例#3
0
 def handle_cast(self, expr: Expression, t: TypeName) -> AnnotatedTypeName:
     # because of the fake solidity check we already know that the cast is possible -> don't have to check if cast possible
     if expr.annotated_type.is_private():
         expected = AnnotatedTypeName(expr.annotated_type.type_name,
                                      Expression.me_expr())
         if not expr.instanceof(expected):
             raise TypeMismatchException(expected, expr.annotated_type,
                                         expr)
         return AnnotatedTypeName(t.clone(), Expression.me_expr())
     else:
         return AnnotatedTypeName(t.clone())
示例#4
0
    def assign_location(target: Expression, source: Expression):
        # set statement
        target.statement = source.statement

        # set parents
        target.parent = source.parent
        target.annotated_type.parent = target
        source.parent = target

        # set source location
        target.line = source.line
        target.column = source.column
示例#5
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))
示例#6
0
    def make_private(expr: Expression, privacy: Expression,
                     homomorphism: Homomorphism):
        assert (privacy.privacy_annotation_label() is not None)

        pl = get_privacy_expr_from_label(privacy.privacy_annotation_label())
        r = ReclassifyExpr(expr, pl, homomorphism)

        # set type
        r.annotated_type = AnnotatedTypeName(expr.annotated_type.type_name,
                                             pl.clone(), homomorphism)
        TypeCheckVisitor.check_for_invalid_private_type(r)

        # set statement, parents, location
        TypeCheckVisitor.assign_location(r, expr)

        return r
示例#7
0
    def get_rhs(self, rhs: Expression, expected_type: AnnotatedTypeName):
        if isinstance(rhs, TupleExpr):
            if not isinstance(rhs, TupleExpr) or not isinstance(
                    expected_type.type_name, TupleType) or len(
                        rhs.elements) != len(expected_type.type_name.types):
                raise TypeMismatchException(expected_type, rhs.annotated_type,
                                            rhs)
            exprs = [
                self.get_rhs(a, e)
                for e, a, in zip(expected_type.type_name.types, rhs.elements)
            ]
            return replace_expr(rhs, TupleExpr(exprs)).as_type(
                TupleType([e.annotated_type for e in exprs]))

        instance = rhs.instanceof(expected_type)
        if not instance:
            raise TypeMismatchException(expected_type, rhs.annotated_type, rhs)
        else:
            if rhs.annotated_type.type_name != expected_type.type_name:
                rhs = self.implicitly_converted_to(rhs,
                                                   expected_type.type_name)

            if instance == 'make-private':
                return self.make_private(rhs, expected_type.privacy_annotation)
            else:
                return rhs
示例#8
0
    def visitAnnotatedTypeName(self, ast: AnnotatedTypeName):
        if type(ast.type_name) == UserDefinedTypeName:
            if not isinstance(ast.type_name.target, EnumDefinition):
                raise TypeException('Unsupported use of user-defined type',
                                    ast.type_name)
            ast.type_name = ast.type_name.target.annotated_type.type_name.clone(
            )

        if ast.privacy_annotation != Expression.all_expr():
            if not ast.type_name.can_be_private():
                raise TypeException(
                    f'Currently, we do not support private {str(ast.type_name)}',
                    ast)

        p = ast.privacy_annotation
        if isinstance(p, IdentifierExpr):
            t = p.target
            if isinstance(t, Mapping):
                # no action necessary, this is the case: mapping(address!x => uint@x)
                pass
            elif not t.is_final and not t.is_constant:
                raise TypeException(
                    'Privacy annotations must be "final" or "constant", if they are expressions',
                    p)
            elif t.annotated_type != AnnotatedTypeName.address_all():
                raise TypeException(
                    f'Privacy type is not a public address, but {str(t.annotated_type)}',
                    p)
示例#9
0
 def visitIfStatement(self, ast: IfStatement):
     b = ast.condition
     if not b.instanceof_data_type(TypeName.bool_type()):
         raise TypeMismatchException(TypeName.bool_type(),
                                     b.annotated_type.type_name, b)
     if ast.condition.annotated_type.is_private():
         expected = AnnotatedTypeName(TypeName.bool_type(),
                                      Expression.me_expr())
         if not b.instanceof(expected):
             raise TypeMismatchException(expected, b.annotated_type, b)
示例#10
0
    def implicitly_converted_to(expr: Expression, t: TypeName) -> Expression:
        if isinstance(expr, ReclassifyExpr) and not expr.privacy.is_all_expr():
            # Cast the argument of the ReclassifyExpr instead
            expr.expr = TypeCheckVisitor.implicitly_converted_to(expr.expr, t)
            expr.annotated_type.type_name = expr.expr.annotated_type.type_name
            return expr

        assert expr.annotated_type.type_name.is_primitive_type()
        cast = PrimitiveCastExpr(t.clone(), expr, is_implicit=True).override(
            parent=expr.parent,
            statement=expr.statement,
            line=expr.line,
            column=expr.column)
        cast.elem_type.parent = cast
        expr.parent = cast
        cast.annotated_type = AnnotatedTypeName(
            t.clone(), expr.annotated_type.privacy_annotation.clone(),
            expr.annotated_type.homomorphism).override(parent=cast)
        return cast
示例#11
0
文件: deep_copy.py 项目: nibau/zkay
def replace_expr(old_expr: Expression,
                 new_expr: Expression,
                 copy_type: bool = False):
    """
        Copies over ast common ast attributes and reruns, parent setter, symbol table, side effect detector
    """
    _replace_ast(old_expr, new_expr)
    if copy_type:
        new_expr.annotated_type = old_expr.annotated_type
    return new_expr
示例#12
0
 def implicitly_converted_to(expr: Expression, t: TypeName) -> Expression:
     assert expr.annotated_type.type_name.is_primitive_type()
     cast = PrimitiveCastExpr(t.clone(), expr, is_implicit=True).override(
         parent=expr.parent,
         statement=expr.statement,
         line=expr.line,
         column=expr.column)
     cast.elem_type.parent = cast
     expr.parent = cast
     cast.annotated_type = AnnotatedTypeName(
         t.clone(),
         expr.annotated_type.privacy_annotation.clone()).override(
             parent=cast)
     return cast
示例#13
0
    def make_private(expr: Expression, privacy: Expression):
        assert (privacy.privacy_annotation_label() is not None)

        pl = get_privacy_expr_from_label(privacy.privacy_annotation_label())
        r = ReclassifyExpr(expr, pl)

        # set type
        r.annotated_type = AnnotatedTypeName(expr.annotated_type.type_name,
                                             pl.clone())
        TypeCheckVisitor.check_for_invalid_private_type(r)

        # set statement
        r.statement = expr.statement

        # set parents
        r.parent = expr.parent
        r.annotated_type.parent = r
        expr.parent = r

        # set source location
        r.line = expr.line
        r.column = expr.column

        return r
示例#14
0
    def ensure_parameter_encryption(self, insert_loc_stmt: Statement,
                                    param: Parameter):
        """
        Make circuit prove that the encryption of the specified parameter is correct.
        """
        assert param.annotated_type.is_cipher()

        plain_idf = self._secret_input_name_factory.add_idf(
            param.idf.name, param.annotated_type.zkay_type.type_name)
        name = f'{self._in_name_factory.get_new_name(param.annotated_type.type_name)}_{param.idf.name}'
        cipher_idf = self._in_name_factory.add_idf(
            name, param.annotated_type.type_name)
        self._ensure_encryption(insert_loc_stmt, plain_idf,
                                Expression.me_expr(),
                                param.annotated_type.type_name.crypto_params,
                                cipher_idf, True, False)
示例#15
0
    def evaluate_expr_in_circuit(self, expr: Expression,
                                 new_privacy: PrivacyLabelExpr,
                                 homomorphism: Homomorphism) -> LocationExpr:
        """
        Evaluate private expression and return result as a fresh out variable.

        Roughly corresponds to out() from paper

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

        :param expr: [SIDE EFFECT] The expression which should be evaluated privately
        :param new_privacy: The circuit output should be encrypted for this owner (or plain if 'all')
        :return: Location expression which references the encrypted circuit result
        """
        with self.circ_indent_block(expr.code()):
            return self._get_circuit_output_for_private_expression(
                expr, new_privacy, homomorphism)
示例#16
0
    def visitAnnotatedTypeName(self, ast: AnnotatedTypeName):
        if type(ast.type_name) == UserDefinedTypeName:
            if not isinstance(ast.type_name.target, EnumDefinition):
                raise TypeException('Unsupported use of user-defined type',
                                    ast.type_name)
            ast.type_name = ast.type_name.target.annotated_type.type_name.clone(
            )

        if ast.privacy_annotation != Expression.all_expr():
            if not ast.type_name.can_be_private():
                raise TypeException(
                    f'Currently, we do not support private {str(ast.type_name)}',
                    ast)
            if ast.homomorphism != Homomorphism.NON_HOMOMORPHIC:
                # only support uint8, uint16, uint24, uint32 homomorphic data types
                if not ast.type_name.is_numeric:
                    raise TypeException(
                        f'Homomorphic type not supported for {str(ast.type_name)}: Only numeric types supported',
                        ast)
                elif ast.type_name.signed:
                    raise TypeException(
                        f'Homomorphic type not supported for {str(ast.type_name)}: Only unsigned types supported',
                        ast)
                elif ast.type_name.elem_bitwidth > 32:
                    raise TypeException(
                        f'Homomorphic type not supported for {str(ast.type_name)}: Only up to 32-bit numeric types supported',
                        ast)

        p = ast.privacy_annotation
        if isinstance(p, IdentifierExpr):
            t = p.target
            if isinstance(t, Mapping):
                # no action necessary, this is the case: mapping(address!x => uint@x)
                pass
            elif not t.is_final and not t.is_constant:
                raise TypeException(
                    'Privacy annotations must be "final" or "constant", if they are expressions',
                    p)
            elif t.annotated_type != AnnotatedTypeName.address_all():
                raise TypeException(
                    f'Privacy type is not a public address, but {str(t.annotated_type)}',
                    p)
示例#17
0
    def visitReclassifyExpr(self, ast: ReclassifyExpr):
        if self.inside_privif_stmt and not ast.statement.before_analysis.same_partition(
                ast.privacy.privacy_annotation_label(), Expression.me_expr()):
            raise TypeException(
                'Revealing information to other parties is not allowed inside private if statements',
                ast)

        if ast.expr.annotated_type.is_public():
            eval_in_public = False
            try:
                self.priv_setter.set_evaluation(ast, evaluate_privately=True)
            except TypeException:
                eval_in_public = True
            if eval_in_public or not self.should_evaluate_public_expr_in_circuit(
                    ast.expr):
                self.priv_setter.set_evaluation(ast.expr,
                                                evaluate_privately=False)
        else:
            self.priv_setter.set_evaluation(ast, evaluate_privately=True)
        self.visit(ast.expr)
示例#18
0
    def visitIndexExpr(self, ast: IndexExpr):
        arr = ast.arr
        index = ast.key

        map_t = arr.annotated_type
        # should have already been checked
        assert (map_t.privacy_annotation.is_all_expr())

        # do actual type checking
        if isinstance(map_t.type_name, Mapping):
            key_type = map_t.type_name.key_type
            expected = AnnotatedTypeName(key_type, Expression.all_expr())
            instance = index.instanceof(expected)
            if not instance:
                raise TypeMismatchException(expected, index.annotated_type,
                                            ast)

            # record indexing information
            if map_t.type_name.key_label is not None:  # TODO modification correct?
                if index.privacy_annotation_label():
                    map_t.type_name.instantiated_key = index
                else:
                    raise TypeException(
                        f'Index cannot be used as a privacy type for array of type {map_t}',
                        ast)

            # determine value type
            ast.annotated_type = map_t.type_name.value_type

            if not self.is_accessible_by_invoker(ast):
                raise TypeException(
                    "Tried to read value which cannot be proven to be owned by the transaction invoker",
                    ast)
        elif isinstance(map_t.type_name, Array):
            if ast.key.annotated_type.is_private():
                raise TypeException('No private array index', ast)
            if not ast.key.instanceof_data_type(TypeName.number_type()):
                raise TypeException('Array index must be numeric', ast)
            ast.annotated_type = map_t.type_name.value_type
        else:
            raise TypeException('Indexing into non-mapping', ast)
示例#19
0
 def visitIfStatement(self, ast: IfStatement):
     old_in_privif_stmt = self.inside_privif_stmt
     if ast.condition.annotated_type.is_private():
         mod_vals = set(ast.then_branch.modified_values.keys())
         if ast.else_branch is not None:
             mod_vals = mod_vals.union(ast.else_branch.modified_values)
         for val in mod_vals:
             if not val.target.annotated_type.zkay_type.type_name.is_primitive_type(
             ):
                 raise TypeException(
                     'Writes to non-primitive type variables are not allowed inside private if statements',
                     ast)
             if val.in_scope_at(
                     ast) and not ast.before_analysis.same_partition(
                         val.privacy, Expression.me_expr()):
                 raise TypeException(
                     'If statement with private condition must not contain side effects to variables with owner != me',
                     ast)
         self.inside_privif_stmt = True
         self.priv_setter.set_evaluation(ast, evaluate_privately=True)
     self.visitChildren(ast)
     self.inside_privif_stmt = old_in_privif_stmt
示例#20
0
    def create_external_wrapper_body(int_fct: ConstructorOrFunctionDefinition,
                                     ext_circuit: CircuitHelper,
                                     original_params: List[Parameter],
                                     requires_proof: bool) -> Block:
        """
        Return Block with external wrapper function body.

        :param int_fct: corresponding internal function
        :param ext_circuit: [SIDE EFFECT] circuit helper of the external wrapper function
        :param original_params: list of transformed function parameters without additional parameters added due to transformation
        :return: body with wrapper code
        """
        has_priv_args = any(
            [p.annotated_type.is_cipher() for p in original_params])
        stmts = []

        if has_priv_args:
            ext_circuit._require_public_key_for_label_at(
                None, Expression.me_expr())
        if cfg.is_symmetric_cipher():
            # Make sure msg.sender's key pair is available in the circuit
            assert any(isinstance(k, MeExpr) for k in ext_circuit.requested_global_keys) \
                   or has_priv_args, "requires verification => both sender keys required"
            stmts += ext_circuit.request_private_key()

        # Verify that out parameter has correct size
        stmts += [
            RequireStatement(
                IdentifierExpr(cfg.zk_out_name).dot('length').binop(
                    '==', NumberLiteralExpr(ext_circuit.out_size_trans)))
        ]

        # IdentifierExpr for array var holding serialized public circuit inputs
        in_arr_var = IdentifierExpr(cfg.zk_in_name).as_type(
            Array(AnnotatedTypeName.uint_all()))

        # Find index of me's public key in requested_global_keys
        glob_me_key_index = -1
        for idx, e in enumerate(ext_circuit.requested_global_keys):
            if isinstance(e, MeExpr):
                glob_me_key_index = idx
                break

        # Request static public keys
        offset = 0
        key_req_stmts = []
        if ext_circuit.requested_global_keys:
            # Ensure that me public key is stored starting at in[0]
            keys = [key for key in ext_circuit.requested_global_keys]
            if glob_me_key_index != -1:
                (keys[0], keys[glob_me_key_index]) = (keys[glob_me_key_index],
                                                      keys[0])

            tmp_key_var = Identifier('_tmp_key')
            key_req_stmts.append(
                tmp_key_var.decl_var(AnnotatedTypeName.key_type()))
            for key_owner in keys:
                idf, assignment = ext_circuit.request_public_key(
                    key_owner, ext_circuit.get_glob_key_name(key_owner))
                assignment.lhs = IdentifierExpr(tmp_key_var.clone())
                key_req_stmts.append(assignment)

                # Manually add to circuit inputs
                key_req_stmts.append(
                    in_arr_var.slice(offset, cfg.key_len).assign(
                        IdentifierExpr(tmp_key_var.clone()).slice(
                            0, cfg.key_len)))
                offset += cfg.key_len
                assert offset == ext_circuit.in_size

        # Check encrypted parameters
        param_stmts = []
        for p in original_params:
            """ * of T_e rule 8 """
            if p.annotated_type.is_cipher():
                assign_stmt = in_arr_var.slice(
                    offset, cfg.cipher_payload_len).assign(
                        IdentifierExpr(p.idf.clone()).slice(
                            0, cfg.cipher_payload_len))
                ext_circuit.ensure_parameter_encryption(assign_stmt, p)

                # Manually add to circuit inputs
                param_stmts.append(assign_stmt)
                offset += cfg.cipher_payload_len

        if cfg.is_symmetric_cipher():
            # Populate sender field of encrypted parameters
            copy_stmts = []
            for p in original_params:
                if p.annotated_type.is_cipher():
                    sender_key = in_arr_var.index(0)
                    idf = IdentifierExpr(p.idf.clone()).as_type(
                        p.annotated_type.clone())
                    lit = ArrayLiteralExpr([
                        idf.clone().index(i)
                        for i in range(cfg.cipher_payload_len)
                    ] + [sender_key])
                    copy_stmts.append(
                        VariableDeclarationStatement(
                            VariableDeclaration([], p.annotated_type.clone(),
                                                p.idf.clone(), 'memory'), lit))
            if copy_stmts:
                param_stmts += [
                    Comment(),
                    Comment(
                        'Copy from calldata to memory and set sender field')
                ] + copy_stmts

            assert glob_me_key_index != -1, "Symmetric cipher but did not request me key"

        # Declare in array
        new_in_array_expr = NewExpr(
            AnnotatedTypeName(TypeName.dyn_uint_array()),
            [NumberLiteralExpr(ext_circuit.in_size_trans)])
        in_var_decl = in_arr_var.idf.decl_var(TypeName.dyn_uint_array(),
                                              new_in_array_expr)
        stmts.append(in_var_decl)
        stmts.append(Comment())
        stmts += Comment.comment_wrap_block('Request static public keys',
                                            key_req_stmts)
        stmts += Comment.comment_wrap_block(
            'Backup private arguments for verification', param_stmts)

        # Call internal function
        args = [IdentifierExpr(param.idf.clone()) for param in original_params]
        internal_call = FunctionCallExpr(
            IdentifierExpr(int_fct.idf.clone()).override(target=int_fct), args)
        internal_call.sec_start_offset = ext_circuit.priv_in_size

        if int_fct.requires_verification:
            ext_circuit.call_function(internal_call)
            args += [
                in_arr_var.clone(),
                NumberLiteralExpr(ext_circuit.in_size),
                IdentifierExpr(cfg.zk_out_name),
                NumberLiteralExpr(ext_circuit.out_size)
            ]

        if int_fct.return_parameters:
            stmts += Comment.comment_list("Declare return variables", [
                VariableDeclarationStatement(deep_copy(vd))
                for vd in int_fct.return_var_decls
            ])
            in_call = TupleExpr([
                IdentifierExpr(vd.idf.clone())
                for vd in int_fct.return_var_decls
            ]).assign(internal_call)
        else:
            in_call = ExpressionStatement(internal_call)
        stmts.append(Comment("Call internal function"))
        stmts.append(in_call)
        stmts.append(Comment())

        # Call verifier
        if requires_proof:
            verifier = IdentifierExpr(
                cfg.get_contract_var_name(
                    ext_circuit.verifier_contract_type.code()))
            verifier_args = [
                IdentifierExpr(cfg.proof_param_name),
                IdentifierExpr(cfg.zk_in_name),
                IdentifierExpr(cfg.zk_out_name)
            ]
            verify = ExpressionStatement(
                verifier.call(cfg.verification_function_name, verifier_args))
            stmts.append(
                StatementList(
                    [Comment('Verify zk proof of execution'), verify],
                    excluded_from_simulation=True))

        # Add return statement at the end if necessary
        if int_fct.return_parameters:
            stmts.append(
                ReturnStatement(
                    TupleExpr([
                        IdentifierExpr(vd.idf.clone())
                        for vd in int_fct.return_var_decls
                    ])))

        return Block(stmts)
示例#21
0
 def visitExpression(self, ast: Expression):
     parent = ast
     while parent and not isinstance(parent, Statement):
         parent = parent.parent
     if parent:
         ast.statement = parent
示例#22
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
示例#23
0
    def _get_circuit_output_for_private_expression(
            self, expr: Expression, new_privacy: PrivacyLabelExpr,
            homomorphism: Homomorphism) -> LocationExpr:
        """
        Add evaluation of expr to the circuit and return the output HybridArgumentIdf corresponding to the evaluation result.

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

        :param expr: [SIDE EFFECT] expression to evaluate
        :param new_privacy: result owner (determines encryption key)
        :return: HybridArgumentIdf which references the circuit output containing the result of expr
        """
        is_circ_val = isinstance(expr, IdentifierExpr) and isinstance(
            expr.idf, HybridArgumentIdf
        ) and expr.idf.arg_type != HybridArgType.PUB_CONTRACT_VAL
        is_hom_comp = isinstance(expr, FunctionCallExpr) and isinstance(
            expr.func, BuiltinFunction
        ) and expr.func.homomorphism != Homomorphism.NON_HOMOMORPHIC
        if is_hom_comp:
            # Treat a homomorphic operation as a privately evaluated operation on (public) ciphertexts
            expr.annotated_type = AnnotatedTypeName.cipher_type(
                expr.annotated_type, homomorphism)

        if is_circ_val or expr.annotated_type.is_private(
        ) or expr.evaluate_privately:
            priv_result_idf = self._evaluate_private_expression(expr)
        else:
            # For public expressions which should not be evaluated in private, only the result is moved into the circuit
            priv_result_idf = self.add_to_circuit_inputs(expr)
        private_expr = priv_result_idf.get_idf_expr()

        t_suffix = ''
        if isinstance(expr, IdentifierExpr) and not is_circ_val:
            t_suffix += f'_{expr.idf.name}'

        if isinstance(new_privacy,
                      AllExpr) or expr.annotated_type.type_name.is_cipher():
            # If the result is public, add an equality constraint to ensure that the user supplied public output
            # is equal to the circuit evaluation result
            tname = f'{self._out_name_factory.get_new_name(expr.annotated_type.type_name)}{t_suffix}'
            new_out_param = self._out_name_factory.add_idf(
                tname, expr.annotated_type.type_name, private_expr)
            self._phi.append(CircEqConstraint(priv_result_idf, new_out_param))
            out_var = new_out_param.get_loc_expr().explicitly_converted(
                expr.annotated_type.type_name)
        else:
            # If the result is encrypted, add an encryption constraint to ensure that the user supplied encrypted output
            # is equal to the correctly encrypted circuit evaluation result
            new_privacy = self._get_canonical_privacy_label(
                expr.analysis, new_privacy)
            privacy_label_expr = get_privacy_expr_from_label(new_privacy)
            cipher_t = TypeName.cipher_type(expr.annotated_type, homomorphism)
            tname = f'{self._out_name_factory.get_new_name(cipher_t)}{t_suffix}'
            enc_expr = EncryptionExpression(private_expr, privacy_label_expr,
                                            homomorphism)
            new_out_param = self._out_name_factory.add_idf(
                tname, cipher_t, enc_expr)
            crypto_params = cfg.get_crypto_params(homomorphism)
            self._ensure_encryption(expr.statement, priv_result_idf,
                                    new_privacy, crypto_params, new_out_param,
                                    False, False)
            out_var = new_out_param.get_loc_expr()

        # Add an invisible CircuitComputationStatement to the solidity code, which signals the offchain simulator,
        # that the value the contained out variable must be computed at this point by simulating expression evaluation
        expr.statement.pre_statements.append(
            CircuitComputationStatement(new_out_param))
        return out_var
示例#24
0
    def evaluate_stmt_in_circuit(self, ast: Statement) -> AssignmentStatement:
        """
        Evaluate an entire statement privately.

        This works by turning the statement into an assignment statement where the

        * lhs is a tuple of all external locations (defined outside statement), which are modified inside the statement
        * rhs is the return value of an inlined function call expression to a virtual function where body = the statement + return statement \
          which returns a tuple of the most recent SSA version of all modified locations

        Note: Modifying external locations which are not owned by @me inside the statement is illegal (would leak information).
        Note: At the moment, this is only used for if statements with a private condition.

        :param ast: the statement to evaluate inside the circuit
        :return: AssignmentStatement as described above
        """
        astmt = ExpressionStatement(NumberLiteralExpr(0))
        for var in ast.modified_values:
            if var.in_scope_at(ast):
                astmt = AssignmentStatement(None, None)
                break

        astmt.before_analysis = ast.before_analysis

        # External values written inside statement -> function return values
        ret_params = []
        for var in ast.modified_values:
            if var.in_scope_at(ast):
                # side effect affects location outside statement and has privacy @me
                assert ast.before_analysis.same_partition(
                    var.privacy, Expression.me_expr())
                assert isinstance(
                    var.target,
                    (Parameter, VariableDeclaration, StateVariableDeclaration))
                t = var.target.annotated_type.zkay_type
                if not t.type_name.is_primitive_type():
                    raise NotImplementedError(
                        'Reference types inside private if statements are not supported'
                    )
                ret_t = AnnotatedTypeName(t.type_name, Expression.me_expr(),
                                          t.homomorphism)  # t, but @me
                ret_param = IdentifierExpr(var.target.idf.clone(),
                                           ret_t).override(target=var.target)
                ret_param.statement = astmt
                ret_params.append(ret_param)

        # Build the imaginary function
        fdef = ConstructorOrFunctionDefinition(
            Identifier('<stmt_fct>'), [], ['private'], [
                Parameter([], ret.annotated_type, ret.target.idf)
                for ret in ret_params
            ], Block([ast, ReturnStatement(TupleExpr(ret_params))]))
        fdef.original_body = fdef.body
        fdef.body.parent = fdef
        fdef.parent = ast

        # inline "Call" to the imaginary function
        fcall = FunctionCallExpr(
            IdentifierExpr('<stmt_fct>').override(target=fdef), [])
        fcall.statement = astmt
        ret_args = self.inline_function_call_into_circuit(fcall)

        # Move all return values out of the circuit
        if not isinstance(ret_args, TupleExpr):
            ret_args = TupleExpr([ret_args])
        for ret_arg in ret_args.elements:
            ret_arg.statement = astmt
        ret_arg_outs = [
            self._get_circuit_output_for_private_expression(
                ret_arg, Expression.me_expr(),
                ret_param.annotated_type.homomorphism)
            for ret_param, ret_arg in zip(ret_params, ret_args.elements)
        ]

        # Create assignment statement
        if ret_params:
            astmt.lhs = TupleExpr(
                [ret_param.clone() for ret_param in ret_params])
            astmt.rhs = TupleExpr(ret_arg_outs)
            return astmt
        else:
            assert isinstance(astmt, ExpressionStatement)
            return astmt
示例#25
0
    def transform_contract(self, su: SourceUnit, c: ContractDefinition) -> ContractDefinition:
        """
        Transform an entire zkay contract into a public solidity contract.

        This:

        * transforms state variables, function bodies and signatures
        * import verification contracts
        * adds zk_data structs for each function with verification \
          (to store circuit I/O, to bypass solidity stack limit and allow for easy assignment of array variables),
        * creates external wrapper functions for all public functions which require verification
        * adds circuit IO serialization/deserialization code from/to zk_data struct to all functions which require verification.

        :param su: [SIDE EFFECTS] Source unit of which this contract is part of
        :param c: [SIDE EFFECTS] The contract to transform
        :return: The contract itself
        """

        all_fcts = c.constructor_definitions + c.function_definitions

        # Get list of static owner labels for this contract
        global_owners = [Expression.me_expr()]
        for var in c.state_variable_declarations:
            if var.annotated_type.is_address() and (var.is_final or var.is_constant):
                global_owners.append(var.idf)

        # Backup untransformed function bodies
        for fct in all_fcts:
            fct.original_body = deep_copy(fct.body, with_types=True, with_analysis=True)

        # Transform types of normal state variables
        c.state_variable_declarations = self.var_decl_trafo.visit_list(c.state_variable_declarations)

        # Split into functions which require verification and those which don't need a circuit helper
        req_ext_fcts = {}
        new_fcts, new_constr = [], []
        for fct in all_fcts:
            assert isinstance(fct, ConstructorOrFunctionDefinition)
            if fct.requires_verification or fct.requires_verification_when_external:
                self.circuits[fct] = self.create_circuit_helper(fct, global_owners)

            if fct.requires_verification_when_external:
                req_ext_fcts[fct] = fct.parameters[:]
            elif fct.is_constructor:
                new_constr.append(fct)
            else:
                new_fcts.append(fct)

        # Add constant state variables for external contracts and field prime
        field_prime_decl = StateVariableDeclaration(AnnotatedTypeName.uint_all(), ['public', 'constant'],
                                                    Identifier(cfg.field_prime_var_name),
                                                    NumberLiteralExpr(bn128_scalar_field))
        contract_var_decls = self.include_verification_contracts(su, c)
        c.state_variable_declarations = [field_prime_decl, Comment()]\
                                        + Comment.comment_list('Helper Contracts', contract_var_decls)\
                                        + [Comment('User state variables')]\
                                        + c.state_variable_declarations

        # Transform signatures
        for f in all_fcts:
            f.parameters = self.var_decl_trafo.visit_list(f.parameters)
        for f in c.function_definitions:
            f.return_parameters = self.var_decl_trafo.visit_list(f.return_parameters)
            f.return_var_decls = self.var_decl_trafo.visit_list(f.return_var_decls)

        # Transform bodies
        for fct in all_fcts:
            gen = self.circuits.get(fct, None)
            fct.body = ZkayStatementTransformer(gen).visit(fct.body)

        # Transform (internal) functions which require verification (add the necessary additional parameters and boilerplate code)
        fcts_with_verification = [fct for fct in all_fcts if fct.requires_verification]
        compute_transitive_circuit_io_sizes(fcts_with_verification, self.circuits)
        transform_internal_calls(fcts_with_verification, self.circuits)
        for f in fcts_with_verification:
            circuit = self.circuits[f]
            assert circuit.requires_verification()
            if circuit.requires_zk_data_struct():
                # Add zk data struct for f to contract
                zk_data_struct = StructDefinition(Identifier(circuit.zk_data_struct_name), [
                    VariableDeclaration([], AnnotatedTypeName(idf.t), idf.clone(), '')
                    for idf in circuit.output_idfs + circuit.input_idfs
                ])
                c.struct_definitions.append(zk_data_struct)
            self.create_internal_verification_wrapper(f)

        # Create external wrapper functions where necessary
        for f, params in req_ext_fcts.items():
            ext_f, int_f = self.split_into_external_and_internal_fct(f, params, global_owners)
            if ext_f.is_function:
                new_fcts.append(ext_f)
            else:
                new_constr.append(ext_f)
            new_fcts.append(int_f)

        c.constructor_definitions = new_constr
        c.function_definitions = new_fcts
        return c
示例#26
0
    def visitFunctionCallExpr(self, ast: FunctionCallExpr):
        if isinstance(ast.func, BuiltinFunction):
            if ast.func.is_private:
                """
                Modified Rule (12) builtin functions with private operands are evaluated inside the circuit.

                A private expression on its own (like an IdentifierExpr referring to a private variable) is not enough to trigger a
                boundary crossing (assignment of private variables is a public operation).
                """
                return self.gen.evaluate_expr_in_circuit(ast, Expression.me_expr())
            else:
                """
                Rule (10) with additional short-circuit handling.

                Builtin operations on public operands are normally left untransformed, but if the builtin function has
                short-circuiting semantics, guard conditions must be added if any of the public operands contains
                nested private expressions.
                """
                # handle short-circuiting
                if ast.func.has_shortcircuiting() and any(map(contains_private_expr, ast.args[1:])):
                    op = ast.func.op
                    guard_var = self.gen.add_to_circuit_inputs(ast.args[0])
                    ast.args[0] = guard_var.get_loc_expr(ast)
                    if op == 'ite':
                        ast.args[1] = self.visit_guarded_expression(guard_var, True, ast.args[1])
                        ast.args[2] = self.visit_guarded_expression(guard_var, False, ast.args[2])
                    elif op == '||':
                        ast.args[1] = self.visit_guarded_expression(guard_var, False, ast.args[1])
                    elif op == '&&':
                        ast.args[1] = self.visit_guarded_expression(guard_var, True, ast.args[1])
                    return ast

                return self.visit_children(ast)
        elif ast.is_cast:
            """Casts are handled either in public or inside the circuit depending on the privacy of the casted expression."""
            assert isinstance(ast.func.target, EnumDefinition)
            if ast.args[0].evaluate_privately:
                return self.gen.evaluate_expr_in_circuit(ast, Expression.me_expr())
            else:
                return self.visit_children(ast)
        else:
            """
            Handle normal function calls (outside private expression case).

            The called functions are allowed to have side effects,
            if the function does not require verification it can even be recursive.
            """
            assert isinstance(ast.func, LocationExpr)
            ast = self.visit_children(ast)
            if ast.func.target.requires_verification_when_external:
                # Reroute the function call to the corresponding internal function if the called function was split into external/internal.
                if not isinstance(ast.func, IdentifierExpr):
                    raise NotImplementedError()
                ast.func.idf.name = cfg.get_internal_name(ast.func.target)

            if ast.func.target.requires_verification:
                # If the target function has an associated circuit, make this function's circuit aware of the call.
                self.gen.call_function(ast)
            elif ast.func.target.has_side_effects and self.gen is not None:
                # Invalidate modified state variables for the current circuit
                for val in ast.modified_values:
                    if val.key is None and isinstance(val.target, StateVariableDeclaration):
                        self.gen.invalidate_idf(val.target.idf)

            # The call will be present as a normal function call in the output solidity code.
            return ast
示例#27
0
 def visitPrimitiveCastExpr(self, ast: PrimitiveCastExpr):
     """Casts are handled either in public or inside the circuit depending on the privacy of the casted expression."""
     if ast.evaluate_privately:
         return self.gen.evaluate_expr_in_circuit(ast, Expression.me_expr())
     else:
         return self.visit_children(ast)
示例#28
0
 def visitExpression(self, ast: Expression):
     self.visit_child_expressions(ast, ast.children())
示例#29
0
    def create_external_wrapper_body(int_fct: ConstructorOrFunctionDefinition, ext_circuit: CircuitHelper,
                                     original_params: List[Parameter], requires_proof: bool) -> Block:
        """
        Return Block with external wrapper function body.

        :param int_fct: corresponding internal function
        :param ext_circuit: [SIDE EFFECT] circuit helper of the external wrapper function
        :param original_params: list of transformed function parameters without additional parameters added due to transformation
        :return: body with wrapper code
        """
        priv_args = [p for p in original_params if p.annotated_type.is_cipher()]
        args_backends = OrderedDict.fromkeys([p.annotated_type.type_name.crypto_params for p in priv_args])
        stmts = []

        for crypto_params in args_backends:
            assert crypto_params in int_fct.used_crypto_backends
            # If there are any private arguments with homomorphism 'hom', we need the public key for that crypto backend
            ext_circuit._require_public_key_for_label_at(None, Expression.me_expr(), crypto_params)
        for crypto_params in cfg.all_crypto_params():
            if crypto_params.is_symmetric_cipher():
                if (MeExpr(), crypto_params) in ext_circuit.requested_global_keys or crypto_params in args_backends:
                    # Make sure msg.sender's key pair is available in the circuit
                    stmts += ext_circuit.request_private_key(crypto_params)

        # Verify that out parameter has correct size
        stmts += [RequireStatement(IdentifierExpr(cfg.zk_out_name).dot('length').binop('==', NumberLiteralExpr(ext_circuit.out_size_trans)))]

        # IdentifierExpr for array var holding serialized public circuit inputs
        in_arr_var = IdentifierExpr(cfg.zk_in_name).as_type(Array(AnnotatedTypeName.uint_all()))

        # Request static public keys
        offset = 0
        key_req_stmts = []
        me_key_idx: Dict[CryptoParams, int] = {}
        if ext_circuit.requested_global_keys:
            # Ensure that me public key is stored starting at in[0]
            keys = [key for key in ext_circuit.requested_global_keys]

            tmp_keys = {}
            for crypto_params in int_fct.used_crypto_backends:
                tmp_key_var = Identifier(f'_tmp_key_{crypto_params.identifier_name}')
                key_req_stmts.append(tmp_key_var.decl_var(AnnotatedTypeName.key_type(crypto_params)))
                tmp_keys[crypto_params] = tmp_key_var
            for (key_owner, crypto_params) in keys:
                tmp_key_var = tmp_keys[crypto_params]
                idf, assignment = ext_circuit.request_public_key(crypto_params, key_owner, ext_circuit.get_glob_key_name(key_owner, crypto_params))
                assignment.lhs = IdentifierExpr(tmp_key_var.clone())
                key_req_stmts.append(assignment)

                # Remember me-keys for later use in symmetrically encrypted keys
                if key_owner == MeExpr():
                    assert crypto_params not in me_key_idx
                    me_key_idx[crypto_params] = offset

                # Manually add to circuit inputs
                key_len = crypto_params.key_len
                key_req_stmts.append(in_arr_var.slice(offset, key_len).assign(IdentifierExpr(tmp_key_var.clone()).slice(0, key_len)))
                offset += key_len
                assert offset == ext_circuit.in_size

        # Check encrypted parameters
        param_stmts = []
        for p in original_params:
            """ * of T_e rule 8 """
            if p.annotated_type.is_cipher():
                cipher_payload_len = p.annotated_type.type_name.crypto_params.cipher_payload_len
                assign_stmt = in_arr_var.slice(offset, cipher_payload_len).assign(IdentifierExpr(p.idf.clone()).slice(0, cipher_payload_len))
                ext_circuit.ensure_parameter_encryption(assign_stmt, p)

                # Manually add to circuit inputs
                param_stmts.append(assign_stmt)
                offset += cipher_payload_len

        # Populate sender field of parameters encrypted with a symmetric cipher
        copy_stmts = []
        for p in original_params:
            if p.annotated_type.is_cipher():
                c = p.annotated_type.type_name
                assert isinstance(c, CipherText)
                if c.crypto_params.is_symmetric_cipher():
                    sender_key = in_arr_var.index(me_key_idx[c.crypto_params])
                    idf = IdentifierExpr(p.idf.clone()).as_type(p.annotated_type.clone())
                    cipher_payload_len = cfg.get_crypto_params(p.annotated_type.homomorphism).cipher_payload_len
                    lit = ArrayLiteralExpr([idf.clone().index(i) for i in range(cipher_payload_len)] + [sender_key])
                    copy_stmts.append(VariableDeclarationStatement(VariableDeclaration([], p.annotated_type.clone(), p.idf.clone(), 'memory'), lit))
        if copy_stmts:
            param_stmts += [Comment(), Comment('Copy from calldata to memory and set sender field')] + copy_stmts

        # Declare in array
        new_in_array_expr = NewExpr(AnnotatedTypeName(TypeName.dyn_uint_array()), [NumberLiteralExpr(ext_circuit.in_size_trans)])
        in_var_decl = in_arr_var.idf.decl_var(TypeName.dyn_uint_array(), new_in_array_expr)
        stmts.append(in_var_decl)
        stmts.append(Comment())
        stmts += Comment.comment_wrap_block('Request static public keys', key_req_stmts)
        stmts += Comment.comment_wrap_block('Backup private arguments for verification', param_stmts)

        # Call internal function
        args = [IdentifierExpr(param.idf.clone()) for param in original_params]
        internal_call = FunctionCallExpr(IdentifierExpr(int_fct.idf.clone()).override(target=int_fct), args)
        internal_call.sec_start_offset = ext_circuit.priv_in_size

        if int_fct.requires_verification:
            ext_circuit.call_function(internal_call)
            args += [in_arr_var.clone(), NumberLiteralExpr(ext_circuit.in_size),
                     IdentifierExpr(cfg.zk_out_name), NumberLiteralExpr(ext_circuit.out_size)]

        if int_fct.return_parameters:
            stmts += Comment.comment_list("Declare return variables", [VariableDeclarationStatement(deep_copy(vd)) for vd in int_fct.return_var_decls])
            in_call = TupleExpr([IdentifierExpr(vd.idf.clone()) for vd in int_fct.return_var_decls]).assign(internal_call)
        else:
            in_call = ExpressionStatement(internal_call)
        stmts.append(Comment("Call internal function"))
        stmts.append(in_call)
        stmts.append(Comment())

        # Call verifier
        if requires_proof and not cfg.disable_verification:
            verifier = IdentifierExpr(cfg.get_contract_var_name(ext_circuit.verifier_contract_type.code()))
            verifier_args = [IdentifierExpr(cfg.proof_param_name), IdentifierExpr(cfg.zk_in_name), IdentifierExpr(cfg.zk_out_name)]
            verify = ExpressionStatement(verifier.call(cfg.verification_function_name, verifier_args))
            stmts.append(StatementList([Comment('Verify zk proof of execution'), verify], excluded_from_simulation=True))

        # Add return statement at the end if necessary
        if int_fct.return_parameters:
            stmts.append(ReturnStatement(TupleExpr([IdentifierExpr(vd.idf.clone()) for vd in int_fct.return_var_decls])))

        return Block(stmts)
示例#30
0
 def visitExpression(self, ast: Expression):
     assert self.evaluate_privately is not None
     ast.evaluate_privately = self.evaluate_privately
     self.visitChildren(ast)