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)
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
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})'
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')
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
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
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)