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 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 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 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