コード例 #1
0
ファイル: types.py プロジェクト: eth-sri/zkay
 def get_params(params: CryptoParams = None, crypto_backend: str = None) -> CryptoParams:
     from zkay.config import cfg
     if params is not None:
         return params
     elif crypto_backend is not None:
         return CryptoParams(crypto_backend)
     else:
         return cfg.get_crypto_params(Homomorphism.NON_HOMOMORPHIC)
コード例 #2
0
ファイル: used_homomorphisms.py プロジェクト: eth-sri/zkay
 def used_crypto_backends(
         used_homs: Set[Homomorphism]) -> List[CryptoParams]:
     # Guarantee consistent order
     result = []
     for hom in Homomorphism:
         if hom in used_homs:
             crypto_backend = cfg.get_crypto_params(hom)
             if crypto_backend not in result:
                 result.append(crypto_backend)
     return result
コード例 #3
0
ファイル: offchain_compiler.py プロジェクト: eth-sri/zkay
 def visitEncryptionExpression(self, ast: EncryptionExpression):
     priv_str = 'msg.sender' if isinstance(
         ast.privacy, MeExpr) else self.visit(ast.privacy.clone())
     crypto_params = cfg.get_crypto_params(ast.homomorphism)
     crypto_str = f'crypto_backend="{crypto_params.crypto_name}"'
     plain = self.visit(ast.expr)
     plain_t = ast.expr.annotated_type.type_name
     if plain_t.is_signed_numeric and crypto_params.enc_signed_as_unsigned:
         plain = self.handle_cast(
             plain, UintTypeName(f'uint{plain_t.elem_bitwidth}'))
     return f'{api("enc")}({plain}, {priv_str}, {crypto_str})'
コード例 #4
0
    def visitFunctionCallExpr(self, ast: FunctionCallExpr):
        if isinstance(ast.func, BuiltinFunction):
            assert ast.func.can_be_private()
            args = list(map(self.visit, ast.args))
            if ast.func.is_shiftop():
                assert ast.args[1].annotated_type.type_name.is_literal
                args[1] = ast.args[1].annotated_type.type_name.value

            op = ast.func.op
            op = '-' if op == 'sign-' else op

            homomorphism = ast.func.homomorphism
            if homomorphism == Homomorphism.NON_HOMOMORPHIC:
                f_start = 'o_('
            else:
                crypto_backend = cfg.get_crypto_params(
                    homomorphism).crypto_name
                public_key_name = ast.public_key.name
                f_start = f'o_hom("{crypto_backend}", "{public_key_name}", '
                args = [f'HomomorphicInput.of({arg})' for arg in args]

            if op == 'ite':
                fstr = f"{f_start}{{}}, '?', {{}}, ':', {{}})"
            elif op == 'parenthesis':
                fstr = '({})'
            elif op == 'sign+':
                raise NotImplementedError()
            else:
                o = f"'{op}'" if len(op) == 1 else f'"{op}"'
                if len(args) == 1:
                    fstr = f"{f_start}{o}, {{}})"
                else:
                    assert len(args) == 2
                    fstr = f'{f_start}{{}}, {o}, {{}})'
                    if op == "*" and ast.func.rerand_using is not None:
                        # re-randomize homomorphic scalar multiplication
                        rnd = self.visit(ast.func.rerand_using)
                        fstr = f'o_rerand({fstr}, "{crypto_backend}", "{public_key_name}", {rnd})'

            return fstr.format(*args)
        elif ast.is_cast and isinstance(ast.func.target, EnumDefinition):
            assert ast.annotated_type.type_name.elem_bitwidth == 256
            return self.handle_cast(self.visit(ast.args[0]),
                                    TypeName.uint_type())

        raise ValueError(
            f'Unsupported function {ast.func.code()} inside circuit')
コード例 #5
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
コード例 #6
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
コード例 #7
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)