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
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)
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
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 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())
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())
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 visitForStatement(self, ast: ForStatement): if not ast.condition.instanceof(AnnotatedTypeName.bool_all()): raise TypeMismatchException(AnnotatedTypeName.bool_all(), ast.condition.annotated_type, ast.condition)
def handle_builtin_function_call(self, ast: FunctionCallExpr, func: BuiltinFunction): # handle special cases if func.is_ite(): cond_t = ast.args[0].annotated_type # Ensure that condition is boolean if not cond_t.type_name.implicitly_convertible_to( TypeName.bool_type()): raise TypeMismatchException(TypeName.bool_type(), cond_t.type_name, ast.args[0]) res_t = ast.args[1].annotated_type.type_name.combined_type( ast.args[2].annotated_type.type_name, True) if cond_t.is_private(): # Everything is turned private func.is_private = True a = res_t.annotate(Expression.me_expr()) else: p = ast.args[1].annotated_type.combined_privacy( ast.analysis, ast.args[2].annotated_type) a = res_t.annotate(p) ast.args[1] = self.get_rhs(ast.args[1], a) ast.args[2] = self.get_rhs(ast.args[2], a) ast.annotated_type = a return elif func.is_parenthesis(): ast.annotated_type = ast.args[0].annotated_type return # Check that argument types conform to op signature parameter_types = func.input_types() if not func.is_eq(): for arg, t in zip(ast.args, parameter_types): if not arg.instanceof_data_type(t): raise TypeMismatchException(t, arg.annotated_type.type_name, arg) t1 = ast.args[0].annotated_type.type_name t2 = None if len( ast.args) == 1 else ast.args[1].annotated_type.type_name if len(ast.args) == 1: arg_t = 'lit' if ast.args[ 0].annotated_type.type_name.is_literal else t1 else: assert len(ast.args) == 2 is_eq_with_tuples = func.is_eq() and isinstance(t1, TupleType) arg_t = t1.combined_type(t2, convert_literals=is_eq_with_tuples) # Infer argument and output types if arg_t == 'lit': res = func.op_func( *[arg.annotated_type.type_name.value for arg in ast.args]) if isinstance(res, bool): assert func.output_type() == TypeName.bool_type() out_t = BooleanLiteralType(res) else: assert func.output_type() == TypeName.number_type() out_t = NumberLiteralType(res) if func.is_eq(): arg_t = t1.to_abstract_type().combined_type( t2.to_abstract_type(), True) elif func.output_type() == TypeName.bool_type(): out_t = TypeName.bool_type() else: out_t = arg_t assert arg_t is not None and (arg_t != 'lit' or not func.is_eq()) private_args = any(map(self.has_private_type, ast.args)) if private_args: assert arg_t != 'lit' if func.can_be_private(): if func.is_shiftop(): if not ast.args[1].annotated_type.type_name.is_literal: raise TypeException( 'Private shift expressions must use a constant (literal) shift amount', ast.args[1]) if ast.args[1].annotated_type.type_name.value < 0: raise TypeException('Cannot shift by negative amount', ast.args[1]) if func.is_bitop() or func.is_shiftop(): for arg in ast.args: if arg.annotated_type.type_name.elem_bitwidth == 256: raise TypeException( 'Private bitwise and shift operations are only supported for integer types < 256 bit, ' 'please use a smaller type', arg) if func.is_arithmetic(): for a in ast.args: if a.annotated_type.type_name.elem_bitwidth == 256: issue_compiler_warning( func, 'Possible field prime overflow', 'Private arithmetic 256bit operations overflow at FIELD_PRIME.\n' 'If you need correct overflow behavior, use a smaller integer type.' ) break elif func.is_comp(): for a in ast.args: if a.annotated_type.type_name.elem_bitwidth == 256: issue_compiler_warning( func, 'Possible private comparison failure', 'Private 256bit comparison operations will fail for values >= 2^252.\n' 'If you cannot guarantee that the value stays in range, you must use ' 'a smaller integer type to ensure correctness.' ) break func.is_private = True p = Expression.me_expr() else: raise TypeException( f'Operation \'{func.op}\' does not support private operands', ast) else: p = None if arg_t != 'lit': # Add implicit casts for arguments arg_pt = arg_t.annotate(p) if func.is_shiftop() and p is not None: ast.args[0] = self.get_rhs(ast.args[0], arg_pt) else: ast.args[:] = map( lambda argument: self.get_rhs(argument, arg_pt), ast.args) ast.annotated_type = out_t.annotate(p)