def split_into_external_and_internal_fct(self, f: ConstructorOrFunctionDefinition, original_params: List[Parameter], global_owners: List[PrivacyLabelExpr]) -> Tuple[ConstructorOrFunctionDefinition, ConstructorOrFunctionDefinition]: """ Take public function f and split it into an internal function and an external wrapper function. :param f: [SIDE EFFECT] function to split (at least requires_verification_if_external) :param original_params: list of transformed function parameters without additional parameters added due to transformation :param global_owners: list of static labels (me + final address state variable identifiers) :return: Tuple of newly created external and internal function definitions """ assert f.requires_verification_when_external # Create new empty function with same parameters as original -> external wrapper if f.is_function: new_modifiers = ['external'] original_params = [deep_copy(p, with_types=True).with_changed_storage('memory', 'calldata') for p in original_params] else: new_modifiers = ['public'] if f.is_payable: new_modifiers.append('payable') requires_proof = True if not f.has_side_effects: requires_proof = False new_modifiers.append('view') new_f = ConstructorOrFunctionDefinition(f.idf, original_params, new_modifiers, f.return_parameters, Block([])) # Make original function internal f.idf = Identifier(cfg.get_internal_name(f)) f.modifiers = ['internal' if mod == 'public' else mod for mod in f.modifiers if mod != 'payable'] f.requires_verification_when_external = False # Create new circuit for external function circuit = self.create_circuit_helper(new_f, global_owners, self.circuits[f]) if not f.requires_verification: del self.circuits[f] self.circuits[new_f] = circuit # Set meta attributes and populate body new_f.requires_verification = True new_f.requires_verification_when_external = True new_f.called_functions = f.called_functions new_f.called_functions[f] = None new_f.used_crypto_backends = f.used_crypto_backends new_f.body = self.create_external_wrapper_body(f, circuit, original_params, requires_proof) # Add out and proof parameter to external wrapper storage_loc = 'calldata' if new_f.is_function else 'memory' new_f.add_param(Array(AnnotatedTypeName.uint_all()), Identifier(cfg.zk_out_name), storage_loc) if requires_proof: new_f.add_param(AnnotatedTypeName.proof_type(), Identifier(cfg.proof_param_name), storage_loc) return new_f, f
def inline_function_call_into_circuit( self, fcall: FunctionCallExpr) -> Union[Expression, TupleExpr]: """ Inline an entire function call into the current circuit. :param fcall: Function call to inline :return: Expression (1 retval) / TupleExpr (multiple retvals) with return value(s) """ assert isinstance(fcall.func, LocationExpr) and fcall.func.target is not None fdef = fcall.func.target with self._remapper.remap_scope(fcall.func.target.body): with nullcontext( ) if fcall.func.target.idf.name == '<stmt_fct>' else self.circ_indent_block( f'INLINED {fcall.code()}'): # Assign all arguments to temporary circuit variables which are designated as the current version of the parameter idfs for param, arg in zip(fdef.parameters, fcall.args): self.phi.append( CircComment(f'ARG {param.idf.name}: {arg.code()}')) with self.circ_indent_block(): self.create_new_idf_version_from_value(param.idf, arg) # Visit the untransformed target function body to include all statements in this circuit inlined_body = deep_copy(fdef.original_body, with_types=True, with_analysis=True) self._circ_trafo.visit(inlined_body) fcall.statement.pre_statements += inlined_body.pre_statements # Create TupleExpr with location expressions corresponding to the function return values as elements ret_idfs = [ self._remapper.get_current(vd.idf) for vd in fdef.return_var_decls ] ret = TupleExpr([ IdentifierExpr(idf.clone()).as_type(idf.t) for idf in ret_idfs ]) if len(ret.elements) == 1: # Unpack 1-length tuple ret = ret.elements[0] return ret
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)
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
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)
def test_deep_copy(self): ast = build_ast(self.example.code()) ast_2 = deep_copy(ast) self.assertEqual(str(ast), str(ast_2))