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)
def visitFunctionCallExpr(self, ast: FunctionCallExpr): can_be_private = True has_nonstatic_call = False if ast.evaluate_privately and not ast.is_cast: if isinstance(ast.func, LocationExpr): assert ast.func.target is not None assert isinstance(ast.func.target.annotated_type.type_name, FunctionTypeName) has_nonstatic_call |= not ast.func.target.has_static_body can_be_private &= ast.func.target.can_be_private elif isinstance(ast.func, BuiltinFunction): can_be_private &= (ast.func.can_be_private() or ast.annotated_type.type_name.is_literal) if ast.func.is_eq() or ast.func.is_ite(): can_be_private &= ast.args[ 1].annotated_type.type_name.can_be_private() if has_nonstatic_call: raise TypeException( 'Function calls to non static functions are not allowed inside private expressions', ast) if not can_be_private: raise TypeException( 'Calls to functions with operations which cannot be expressed as a circuit are not allowed inside private expressions', ast) self.visitChildren(ast)
def visit_child_expressions(parent: AST, exprs: List[AST]): if len(exprs) > 1: modset = set(exprs[0].modified_values.keys()) for arg in exprs[1:]: diffset = modset.intersection(arg.modified_values) if diffset: setstr = f'{{{", ".join(map(str, diffset))}}}' raise TypeException( f'Undefined behavior due to potential side effect on the same value(s) \'{setstr}\' in multiple expression children.\n' 'Solidity does not guarantee an evaluation order for non-shortcircuit expressions.\n' 'Since zkay requires local simulation for transaction transformation, all semantics must be well-defined.', parent) else: modset.update(diffset) for arg in exprs: modset = set(arg.modified_values.keys()) other_args = [e for e in exprs if e != arg] for arg2 in other_args: diffset = modset.intersection(arg2.read_values) if diffset: setstr = f'{{{", ".join([str(val) + (f".{str(member)}" if member else "") for val, member in diffset])}}}' raise TypeException( f'Undefined behavior due to read of value(s) \'{setstr}\' which might be modified in this subexpression.\n' 'Solidity does not guarantee an evaluation order for non-shortcircuit expressions.\n' 'Since zkay requires local simulation for transaction transformation, all semantics must be well-defined.', arg)
def visitFunctionCallExpr(self, ast: FunctionCallExpr): if isinstance(ast.func, BuiltinFunction): self.handle_builtin_function_call(ast, ast.func) elif ast.is_cast: if not isinstance(ast.func.target, EnumDefinition): raise NotImplementedError( 'User type casts only implemented for enums') ast.annotated_type = self.handle_cast( ast.args[0], ast.func.target.annotated_type.type_name) elif isinstance(ast.func, LocationExpr): ft = ast.func.annotated_type.type_name if len(ft.parameters) != len(ast.args): raise TypeException("Wrong number of arguments", ast.func) # Check arguments for i in range(len(ast.args)): ast.args[i] = self.get_rhs(ast.args[i], ft.parameters[i].annotated_type) # Set expression type to return type if len(ft.return_parameters) == 1: ast.annotated_type = ft.return_parameters[ 0].annotated_type.clone() else: # TODO maybe not None label in the future ast.annotated_type = AnnotatedTypeName( TupleType([t.annotated_type for t in ft.return_parameters]), None) else: raise TypeException('Invalid function call', ast)
def visitDoWhileStatement(self, ast: DoWhileStatement): if contains_private_expr(ast.condition): raise TypeException( 'Loop condition cannot contain private expressions', ast.condition) if contains_private_expr(ast.body): raise TypeException('Loop body cannot contain private expressions', ast.body) self.visitChildren(ast)
def visitForStatement(self, ast: ForStatement): if contains_private_expr(ast.condition): raise TypeException( 'Loop condition cannot contain private expressions', ast.condition) if contains_private_expr(ast.body): raise TypeException('Loop body cannot contain private expressions', ast.body) if ast.update is not None and contains_private_expr(ast.update): raise TypeException( 'Loop update statement cannot contain private expressions', ast.update) self.visitChildren(ast)
def visitConstructorOrFunctionDefinition( self, ast: ConstructorOrFunctionDefinition): for t in ast.parameter_types: if not isinstance(t.privacy_annotation, (MeExpr, AllExpr)): raise TypeException( 'Only me/all accepted as privacy type of function parameters', ast) if ast.can_be_external: for t in ast.return_type: if not isinstance(t.privacy_annotation, (MeExpr, AllExpr)): raise TypeException( 'Only me/all accepted as privacy type of return values for public functions', ast)
def visitReclassifyExpr(self, ast: ReclassifyExpr): if not ast.privacy.privacy_annotation_label(): raise TypeException( 'Second argument of "reveal" cannot be used as a privacy type', ast) # NB prevent any redundant reveal (not just for public) ast.annotated_type = AnnotatedTypeName( ast.expr.annotated_type.type_name, ast.privacy) if ast.instanceof(ast.expr.annotated_type) is True: raise TypeException( f'Redundant "reveal": Expression is already "@{ast.privacy.code()}"', ast) self.check_for_invalid_private_type(ast)
def visitIdentifierExpr(self, ast: IdentifierExpr): if isinstance(ast.target, Mapping): # no action necessary, the identifier will be replaced later pass else: target = ast.target if isinstance(target, ContractDefinition): raise TypeException( f'Unsupported use of contract type in expression', ast) ast.annotated_type = target.annotated_type.clone() 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)
def handle_homomorphic_builtin_function_call(self, ast: FunctionCallExpr, func: BuiltinFunction): # First - same as non-homomorphic - check that argument types conform to op signature if not func.is_eq(): for arg, t in zip(ast.args, func.input_types()): if not arg.instanceof_data_type(t): raise TypeMismatchException(t, arg.annotated_type.type_name, arg) homomorphic_func = func.select_homomorphic_overload( ast.args, ast.analysis) if homomorphic_func is None: raise TypeException( f'Operation \'{func.op}\' requires all arguments to be accessible, ' f'i.e. @all or provably equal to @me', ast) # We could perform homomorphic operations on-chain by using some Solidity arbitrary precision math library. # For now, keep it simple and evaluate homomorphic operations in Python and check the result in the circuit. func.is_private = True ast.annotated_type = homomorphic_func.output_type() func.homomorphism = ast.annotated_type.homomorphism expected_arg_types = homomorphic_func.input_types() # Check that the argument types are correct ast.args[:] = map(lambda arg, arg_pt: self.get_rhs(arg, arg_pt), ast.args, expected_arg_types)
def visitStateVariableDeclaration(self, ast: StateVariableDeclaration): if ast.expr: # prevent private operations in declaration if contains_private(ast): raise TypeException( 'Private assignments to state variables must be in the constructor', ast) # check type self.get_rhs(ast.expr, ast.annotated_type) # prevent "me" annotation p = ast.annotated_type.privacy_annotation if p.is_me_expr(): raise TypeException(f'State variables cannot be annotated as me', ast)
def visitMemberAccessExpr(self, ast: MemberAccessExpr): assert ast.target is not None if ast.expr.annotated_type.is_address( ) and ast.expr.annotated_type.is_private(): raise TypeException( "Cannot access members of private address variable", ast) ast.annotated_type = ast.target.annotated_type.clone()
def visitIdentifierExpr(self, ast: IdentifierExpr): if ast.is_rvalue() and self.state_vars_assigned is not None: if ast.target in self.state_vars_assigned and not self.state_vars_assigned[ ast.target]: raise TypeException( f'{str(ast)} is reading "final" state variable before writing it', ast)
def visitFunctionCallExpr(self, ast: FunctionCallExpr): if not ast.is_cast and isinstance(ast.func, LocationExpr): if ast.func.target.requires_verification and ast.func.target.is_recursive: raise TypeException( "Non-inlineable call to recursive private function", ast.func) self.visitChildren(ast)
def visitFunctionCallExpr(self, ast: FunctionCallExpr): if self.evaluate_privately and isinstance( ast.func, LocationExpr ) and not ast.is_cast and ast.func.target.has_side_effects: raise TypeException( 'Expressions with side effects are not allowed inside private expressions', ast) self.visitExpression(ast)
def visitAssignmentStatement(self, ast: AssignmentStatement): self.visit(ast.rhs) if isinstance(ast.lhs, IdentifierExpr): var = ast.lhs.target if var in self.state_vars_assigned: if self.state_vars_assigned[var]: raise TypeException("Tried to reassign final variable", ast) self.state_vars_assigned[var] = True
def visitAssignmentStatement(self, ast: AssignmentStatement): if not isinstance(ast.lhs, (TupleExpr, LocationExpr)): raise TypeException("Assignment target is not a location", ast.lhs) expected_type = ast.lhs.annotated_type ast.rhs = self.get_rhs(ast.rhs, expected_type) # prevent modifying final f = ast.function if isinstance(ast.lhs, (IdentifierExpr, TupleExpr)): self.check_final(f, ast.lhs)
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)
def collect_modified_values(self, target: Union[Expression, Statement], expr: Union[TupleExpr, LocationExpr]): if isinstance(expr, TupleExpr): for elem in expr.elements: self.collect_modified_values(target, elem) else: mod_value = InstanceTarget(expr) if mod_value in target.modified_values: raise TypeException( f'Undefined behavior due multiple different assignments to the same target in tuple assignment', expr) target.modified_values[mod_value] = None
def visitReclassifyExpr(self, ast: ReclassifyExpr): if not ast.privacy.privacy_annotation_label(): raise TypeException( 'Second argument of "reveal" cannot be used as a privacy type', ast) homomorphism = ast.homomorphism or ast.expr.annotated_type.homomorphism assert (homomorphism is not None) # Prevent ReclassifyExpr to all with homomorphic type if ast.privacy.is_all_expr( ) and homomorphism != Homomorphism.NON_HOMOMORPHIC: # If the target privacy is all, we infer a target homomorphism of NON_HOMOMORPHIC ast.homomorphism = homomorphism = Homomorphism.NON_HOMOMORPHIC # Make sure the first argument to reveal / rehom is public or private provably equal to @me is_expr_at_all = ast.expr.annotated_type.is_public() is_expr_at_me = ast.expr.annotated_type.is_private_at_me(ast.analysis) if not is_expr_at_all and not is_expr_at_me: raise TypeException( f'First argument of "{ast.func_name()}" must be accessible,' f'i.e. @all or provably equal to @me', ast) # Prevent unhom(public_value) if is_expr_at_all and isinstance( ast, RehomExpr ) and ast.homomorphism == Homomorphism.NON_HOMOMORPHIC: raise TypeException( f'Cannot use "{ast.homomorphism.rehom_expr_name}" on a public value', ast) # NB prevent any redundant reveal (not just for public) ast.annotated_type = AnnotatedTypeName( ast.expr.annotated_type.type_name, ast.privacy, homomorphism) if ast.instanceof(ast.expr.annotated_type) is True: raise TypeException( f'Redundant "{ast.func_name()}": Expression is already ' f'"@{ast.privacy.code()}{homomorphism}"', ast) self.check_for_invalid_private_type(ast)
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
def visitIfStatement(self, ast: IfStatement): self.visit(ast.condition) prev = self.state_vars_assigned.copy() self.visit(ast.then_branch) then_b = self.state_vars_assigned.copy() self.state_vars_assigned = prev if ast.else_branch is not None: self.visit(ast.else_branch) assert then_b.keys() == self.state_vars_assigned.keys() for var in then_b.keys(): if then_b[var] != self.state_vars_assigned[var]: raise TypeException( "Final value is not assigned in both branches", ast)
def visitContractDefinition(self, ast: ContractDefinition): self.state_vars_assigned = {} for v in ast.state_variable_declarations: if v.is_final and v.expr is None: self.state_vars_assigned[v] = False if len(ast.constructor_definitions) > 0: assert (len(ast.constructor_definitions) == 1) c = ast.constructor_definitions[0] self.visit(c.body) for sv, assigned in self.state_vars_assigned.items(): if not assigned: raise TypeException("Did not set all final state variables", sv) self.state_vars_assigned = None
def visitAssignmentStatement(self, ast: AssignmentStatement): # NB TODO? Should we optionally disallow writes to variables which are owned by someone else (with e.g. a new modifier) #if ast.lhs.annotated_type.is_private(): # expected_rhs_type = AnnotatedTypeName(ast.lhs.annotated_type.type_name, Expression.me_expr()) # if not ast.lhs.instanceof(expected_rhs_type): # raise TypeException("Only owner can assign to its private variables", ast) if not isinstance(ast.lhs, (TupleExpr, LocationExpr)): raise TypeException("Assignment target is not a location", ast.lhs) expected_type = ast.lhs.annotated_type ast.rhs = self.get_rhs(ast.rhs, expected_type) # prevent modifying final f = ast.function if isinstance(ast.lhs, (IdentifierExpr, TupleExpr)): self.check_final(f, ast.lhs)
def check_final(self, fct: ConstructorOrFunctionDefinition, ast: Expression): if isinstance(ast, IdentifierExpr): target = ast.target if hasattr(target, 'keywords'): if 'final' in target.keywords: if isinstance( target, StateVariableDeclaration) and fct.is_constructor: # assignment allowed pass else: raise TypeException('Modifying "final" variable', ast) else: assert isinstance(ast, TupleExpr) for elem in ast.elements: self.check_final(fct, elem)
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)
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)
def check_for_invalid_private_type(ast): assert hasattr(ast, 'annotated_type') at = ast.annotated_type if at.is_private() and not at.type_name.can_be_private(): raise TypeException(f"Type {at.type_name} cannot be private", ast.annotated_type)
def visitMapping(self, ast: Mapping): if ast.key_label is not None: if ast.key_type != TypeName.address_type(): raise TypeException(f'Only addresses can be annotated', ast)
def visitRequireStatement(self, ast: RequireStatement): if not ast.condition.annotated_type.privacy_annotation.is_all_expr(): raise TypeException(f'require needs public argument', ast)