def visitCircEncConstraint(self, stmt: CircEncConstraint): assert stmt.cipher.t.is_cipher() assert stmt.pk.t == TypeName.key_type() assert stmt.rnd.t == TypeName.rnd_type() if stmt.is_dec: return f'checkDec("{stmt.plain.name}", "{stmt.pk.name}", "{stmt.rnd.name}", "{stmt.cipher.name}");' else: return f'checkEnc("{stmt.plain.name}", "{stmt.pk.name}", "{stmt.rnd.name}", "{stmt.cipher.name}");'
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 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 implicitly_converted_to(expr: Expression, t: TypeName) -> Expression: assert expr.annotated_type.type_name.is_primitive_type() cast = PrimitiveCastExpr(t.clone(), expr, is_implicit=True).override( parent=expr.parent, statement=expr.statement, line=expr.line, column=expr.column) cast.elem_type.parent = cast expr.parent = cast cast.annotated_type = AnnotatedTypeName( t.clone(), expr.annotated_type.privacy_annotation.clone()).override( parent=cast) return cast
def _require_secret_key(self, crypto_params: CryptoParams) -> HybridArgumentIdf: self._needed_secret_key[ crypto_params] = None # Add to _need_secret_key OrderedDict key_name = self.get_own_secret_key_name(crypto_params) return HybridArgumentIdf(key_name, TypeName.key_type(crypto_params), HybridArgType.PRIV_CIRCUIT_VAL)
def visitFunctionCallExpr(self, ast: FunctionCallExpr): if isinstance(ast.func, BuiltinFunction) and (ast.func.is_arithmetic() or ast.func.op == '~'): # For arithmetic operations, need to simulate finite integer semantics (since python has arbitrary precision ints) t = ast.annotated_type.type_name if ast.annotated_type is not None else TypeName.uint_type( ) res = super().visitFunctionCallExpr(ast) if not t.is_literal and ast.func.homomorphism == Homomorphism.NON_HOMOMORPHIC: # Use cast for correct overflow behavior according to type res = self.handle_cast(res, t) return res elif isinstance(ast.func, BuiltinFunction) and ast.func.is_comp( ) and self.inside_circuit: # Inside circuit, only comparisons with values using less than 252 bits are valid # -> perform additional check args = [ f'{api("range_checked")}({self.visit(a)})' for a in ast.args ] return ast.func.format_string().format(*args) elif ast.is_cast: return self.handle_cast(self.visit(ast.args[0]), ast.func.target.annotated_type.type_name) elif isinstance( ast.func, LocationExpr ) and ast.func.target is not None and ast.func.target.requires_verification: # Function calls to functions which require verification need to be treated differently # (called function has a different priv-value dictionary) f = self.visit(ast.func) a = self.visit_list(ast.args, ', ') return f'{api("call_fct")}({ast.sec_start_offset}, self.{f}, {a})' return super().visitFunctionCallExpr(ast)
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 if op == 'ite': fstr = "o_({}, '?', {}, ':', {})" 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"o_({o}, {{}})" else: assert len(args) == 2 fstr = f'o_({{}}, {o}, {{}})' 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 request_private_key(self, crypto_params: CryptoParams) -> List[Statement]: assert crypto_params in self._needed_secret_key or crypto_params in \ [p.annotated_type.type_name.crypto_params for p in self.fct.parameters if p.annotated_type.is_cipher()] key_name = self.get_own_secret_key_name(crypto_params) self._secret_input_name_factory.add_idf( key_name, TypeName.key_type(crypto_params)) return [EnterPrivateKeyStatement(crypto_params)]
def get_new_name(self, t: TypeName, inc=False) -> str: """ Generate a fresh name for a value of type t. :param t: transformed type :param inc: if True, the internal counter, which is used as part of fresh ids, is incremented """ if t == TypeName.key_type(): postfix = 'key' elif t.is_cipher(): postfix = 'cipher' else: postfix = 'plain' name = f'{self.base_name}{self.count}_{postfix}' if inc: self.count += 1 return name
def implicitly_converted_to(expr: Expression, t: TypeName) -> Expression: if isinstance(expr, ReclassifyExpr) and not expr.privacy.is_all_expr(): # Cast the argument of the ReclassifyExpr instead expr.expr = TypeCheckVisitor.implicitly_converted_to(expr.expr, t) expr.annotated_type.type_name = expr.expr.annotated_type.type_name return expr assert expr.annotated_type.type_name.is_primitive_type() cast = PrimitiveCastExpr(t.clone(), expr, is_implicit=True).override( parent=expr.parent, statement=expr.statement, line=expr.line, column=expr.column) cast.elem_type.parent = cast expr.parent = cast cast.annotated_type = AnnotatedTypeName( t.clone(), expr.annotated_type.privacy_annotation.clone(), expr.annotated_type.homomorphism).override(parent=cast) return cast
def _ensure_encryption(self, stmt: Statement, plain: HybridArgumentIdf, new_privacy: PrivacyLabelExpr, crypto_params: CryptoParams, cipher: HybridArgumentIdf, is_param: bool, is_dec: bool): """ Make sure that cipher = enc(plain, getPk(new_privacy), priv_user_provided_rnd). This automatically requests necessary keys and adds a circuit input for the randomness. Note: This function adds pre-statements to stmt :param stmt [SIDE EFFECT]: the statement which contains the expression which requires this encryption :param plain: circuit variable referencing the plaintext value :param new_privacy: privacy label corresponding to the destination key address :param cipher: circuit variable referencing the encrypted value :param is_param: whether cipher is a function parameter :param is_dec: whether this is a decryption operation (user supplied plain) as opposed to an encryption operation (user supplied cipher) """ if crypto_params.is_symmetric_cipher(): # Need a different set of keys for hybrid-encryption (ecdh-based) backends self._require_secret_key(crypto_params) my_pk = self._require_public_key_for_label_at( stmt, Expression.me_expr(), crypto_params) if is_dec: other_pk = self._get_public_key_in_sender_field( stmt, cipher, crypto_params) else: if new_privacy == Expression.me_expr(): other_pk = my_pk else: other_pk = self._require_public_key_for_label_at( stmt, new_privacy, crypto_params) self.phi.append( CircComment( f'{cipher.name} = enc({plain.name}, ecdh({other_pk.name}, my_sk))' )) self._phi.append( CircSymmEncConstraint(plain, other_pk, cipher, is_dec)) else: rnd = self._secret_input_name_factory.add_idf( f'{plain.name if is_param else cipher.name}_R', TypeName.rnd_type(crypto_params)) pk = self._require_public_key_for_label_at(stmt, new_privacy, crypto_params) if not is_dec: self.phi.append( CircComment( f'{cipher.name} = enc({plain.name}, {pk.name})')) self._phi.append(CircEncConstraint(plain, rnd, pk, cipher, is_dec))
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 request_public_key(self, crypto_params: CryptoParams, plabel: Union[MeExpr, Identifier], name: str): """ Request key for the address corresponding to plabel from pki infrastructure and add it to the public circuit inputs. :param plabel: privacy label for which to request key :param name: name to use for the HybridArgumentIdf holding the key :return: HybridArgumentIdf containing the requested key and an AssignmentStatement which assigns the key request to the idf location """ idf = self._in_name_factory.add_idf(name, TypeName.key_type(crypto_params)) pki = IdentifierExpr( cfg.get_contract_var_name( cfg.get_pki_contract_name(crypto_params))) privacy_label_expr = get_privacy_expr_from_label(plabel) return idf, idf.get_loc_expr().assign( pki.call('getPk', [self._expr_trafo.visit(privacy_label_expr)]))
def _require_public_key_for_label_at( self, stmt: Optional[Statement], privacy: PrivacyLabelExpr, crypto_params: CryptoParams) -> HybridArgumentIdf: """ Make circuit helper aware, that the key corresponding to privacy is required at stmt. If privacy is not a statically known label, the key is requested on spot. Otherwise the label is added to the global key set. The keys in that set are requested only once at the start of the external wrapper function, to improve efficiency. Note: This function has side effects on stmt (adds a pre_statement) :return: HybridArgumentIdf which references the key """ if privacy in self._static_owner_labels: # Statically known privacy -> keep track (all global keys will be requested only once) self._global_keys[(privacy, crypto_params)] = None return HybridArgumentIdf( self.get_glob_key_name(privacy, crypto_params), TypeName.key_type(crypto_params), HybridArgType.PUB_CIRCUIT_ARG) if stmt is None: raise ValueError( 'stmt cannot be None if privacy is not guaranteed to be statically known' ) # privacy cannot be MeExpr (is in _static_owner_labels) or AllExpr (has no public key) assert isinstance(privacy, Identifier) if stmt not in self._requested_dynamic_pks: self._requested_dynamic_pks[stmt] = {} requested_dynamic_pks = self._requested_dynamic_pks[stmt] if privacy in requested_dynamic_pks: return requested_dynamic_pks[privacy] # Dynamic privacy -> always request key on spot and add to local in args name = f'{self._in_name_factory.get_new_name(TypeName.key_type(crypto_params))}_{privacy.name}' idf, get_key_stmt = self.request_public_key(crypto_params, privacy, name) stmt.pre_statements.append(get_key_stmt) requested_dynamic_pks[privacy] = idf return idf
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 _get_public_key_in_sender_field( self, stmt: Statement, cipher: HybridArgumentIdf, crypto_params: CryptoParams) -> HybridArgumentIdf: """ Ensure the circuit has access to the public key stored in cipher's sender field. Note: This function has side effects on stmt [adds a pre-statement] :param stmt [SIDE EFFECT]: statement in which this private expression occurs :param cipher: HybridArgumentIdf which references the cipher value :return: HybridArgumentIdf which references the key in cipher's sender field (or 0 if none) """ key_t = TypeName.key_type(crypto_params) name = f'{self._in_name_factory.get_new_name(key_t)}_sender' key_idf = self._in_name_factory.add_idf(name, key_t) cipher_payload_len = crypto_params.cipher_payload_len key_expr = KeyLiteralExpr( [cipher.get_loc_expr(stmt).index(cipher_payload_len)], crypto_params).as_type(key_t) stmt.pre_statements.append( AssignmentStatement(key_idf.get_loc_expr(), key_expr)) return key_idf
def add_function_circuit_arguments(circuit: CircuitHelper): """Generate java code which adds circuit IO as described by circuit""" input_init_stmts = [] for sec_input in circuit.sec_idfs: input_init_stmts.append( f'addS("{sec_input.name}", {sec_input.t.size_in_uints}, {_get_t(sec_input.t)});' ) for pub_input in circuit.input_idfs: if pub_input.t == TypeName.key_type(): input_init_stmts.append( f'addK("{pub_input.name}", {pub_input.t.size_in_uints});') else: input_init_stmts.append( f'addIn("{pub_input.name}", {pub_input.t.size_in_uints}, {_get_t(pub_input.t)});' ) for pub_output in circuit.output_idfs: input_init_stmts.append( f'addOut("{pub_output.name}", {pub_output.t.size_in_uints}, {_get_t(pub_output.t)});' ) return input_init_stmts
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)
class GlobalDefs: # gasleft: FunctionDefinition = FunctionDefinition( # idf=Identifier('gasleft'), # parameters=[], # modifiers=[], # return_parameters=[Parameter([], annotated_type=AnnotatedTypeName.uint_all(), idf=Identifier(''))], # body=Block([]) # ) # gasleft.idf.parent = gasleft address_struct: StructDefinition = StructDefinition( Identifier('<address>'), [ VariableDeclaration([], AnnotatedTypeName.uint_all(), Identifier('balance')) ]) set_parents(address_struct) address_payable_struct: StructDefinition = StructDefinition( Identifier('<address_payable>'), [ VariableDeclaration([], AnnotatedTypeName.uint_all(), Identifier('balance')), ConstructorOrFunctionDefinition( Identifier('send'), [Parameter([], AnnotatedTypeName.uint_all(), Identifier(''))], ['public'], [Parameter([], AnnotatedTypeName.bool_all(), Identifier(''))], Block([])), ConstructorOrFunctionDefinition( Identifier('transfer'), [Parameter([], AnnotatedTypeName.uint_all(), Identifier(''))], ['public'], [], Block([])), ]) address_payable_struct.members[1].can_be_private = False address_payable_struct.members[2].can_be_private = False set_parents(address_payable_struct) msg_struct: StructDefinition = StructDefinition(Identifier('<msg>'), [ VariableDeclaration([], AnnotatedTypeName(TypeName.address_payable_type()), Identifier('sender')), VariableDeclaration([], AnnotatedTypeName.uint_all(), Identifier('value')), ]) set_parents(msg_struct) block_struct: StructDefinition = StructDefinition(Identifier('<block>'), [ VariableDeclaration([], AnnotatedTypeName(TypeName.address_payable_type()), Identifier('coinbase')), VariableDeclaration([], AnnotatedTypeName.uint_all(), Identifier('difficulty')), VariableDeclaration([], AnnotatedTypeName.uint_all(), Identifier('gaslimit')), VariableDeclaration([], AnnotatedTypeName.uint_all(), Identifier('number')), VariableDeclaration([], AnnotatedTypeName.uint_all(), Identifier('timestamp')), ]) set_parents(block_struct) tx_struct: StructDefinition = StructDefinition(Identifier('<tx>'), [ VariableDeclaration([], AnnotatedTypeName.uint_all(), Identifier('gasprice')), VariableDeclaration([], AnnotatedTypeName(TypeName.address_payable_type()), Identifier('origin')), ]) set_parents(tx_struct)
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 _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 """ 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 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 get_randomness_for_rerand(self, expr: Expression) -> IdentifierExpr: idf = self._secret_input_name_factory.get_new_idf( TypeName.rnd_type(expr.annotated_type.type_name.crypto_params)) return IdentifierExpr(idf)
def visitAnnotatedTypeName(self, ast: AnnotatedTypeName): if ast.is_private(): t = TypeName.cipher_type(ast) else: t = self.visit(ast.type_name.clone()) return AnnotatedTypeName(t)