Ejemplo n.º 1
0
    def visitIdentifierExpr(self, ast: IdentifierExpr):
        # Special identifiers
        pki_inst_names = {
            f'{cfg.get_pki_contract_name(params)}_inst': params
            for params in cfg.all_crypto_params()
        }
        if ast.idf.name in pki_inst_names and not ast.is_lvalue():
            crypto_params = pki_inst_names[ast.idf.name]
            return f'{api("get_keystore")}("{crypto_params.crypto_name}")'
        elif ast.idf.name == cfg.field_prime_var_name:
            assert ast.is_rvalue()
            return f'{SCALAR_FIELD_NAME}'

        if self.current_index:
            # This identifier is the beginning of an Index expression e.g. idf[1][2] or idf[me]
            indices, t = list(reversed(
                self.current_index)), self.current_index_t
            self.current_index, self.current_index_t = [], None
            indices = [self.visit(idx) for idx in indices]
        elif self.inside_circuit and isinstance(
                ast.idf, HybridArgumentIdf
        ) and ast.idf.corresponding_priv_expression is not None and self.flatten_hybrid_args:
            return self.visit(ast.idf.corresponding_priv_expression)
        else:
            indices, t = [], ast.target.annotated_type if isinstance(
                ast.target, StateVariableDeclaration) else None

        return self.get_value(ast, indices)
Ejemplo n.º 2
0
    def _connect_libraries(self):
        zk_print_banner(f'Deploying Libraries')

        sender = self.w3.eth.accounts[0]
        # Since eth-tester is not persistent -> always automatically deploy libraries
        with cfg.library_compilation_environment():
            with tempfile.TemporaryDirectory() as tmpdir:
                with log_context('deploy_pki'):
                    self._pki_contract = {}
                    for crypto_params in cfg.all_crypto_params():
                        with log_context(crypto_params.crypto_name):
                            pki_contract_code = library_contracts.get_pki_contract(crypto_params)
                            pki_contract_name = cfg.get_pki_contract_name(crypto_params)
                            pki_sol = save_to_file(tmpdir, f'{pki_contract_name}.sol', pki_contract_code)
                            contract = self._deploy_contract(sender, self.compile_contract(pki_sol, pki_contract_name))
                            backend_name = crypto_params.crypto_name
                            self._pki_contract[backend_name] = contract
                            zk_print(f'Deployed pki contract for crypto back-end {backend_name} at address "{contract.address}"')

                with log_context('deploy_verify_libs'):
                    verify_sol = save_to_file(tmpdir, 'verify_libs.sol', library_contracts.get_verify_libs_code())
                    self._lib_addresses = {}
                    for lib in cfg.external_crypto_lib_names:
                        out = self._deploy_contract(sender, self.compile_contract(verify_sol, lib))
                        self._lib_addresses[lib] = out.address
                        zk_print(f'Deployed crypto lib {lib} at address "{out.address}"')
Ejemplo n.º 3
0
 def initialize_keys_for(address: Union[bytes, str]):
     """Generate/Load keys for the given address."""
     account = AddressValue(address)
     for crypto_params in cfg.all_crypto_params():
         if not Runtime.keystore(crypto_params).has_initialized_keys_for(
                 AddressValue(address)):
             Runtime.crypto(crypto_params).generate_or_load_key_pair(
                 account)
Ejemplo n.º 4
0
 def _deploy_dependencies(self, sender: Union[bytes, str], project_dir: str, verifier_names: List[str]) -> Dict[str, AddressValue]:
     # Deploy verification contracts if not already done
     vf = {}
     for verifier_name in verifier_names:
         with log_context('constructor'):
             with log_context(f'{verifier_name}'):
                 filename = os.path.join(project_dir, f'{verifier_name}.sol')
                 cout = self.compile_contract(filename, verifier_name, self.lib_addresses)
                 with time_measure("transaction_full"):
                     vf[verifier_name] = AddressValue(self._deploy_contract(sender, cout).address)
     for crypto_params in cfg.all_crypto_params():
         pki_contract_name = cfg.get_pki_contract_name(crypto_params)
         pki_contract_address = self.pki_contract(crypto_params.crypto_name).address
         vf[pki_contract_name] = AddressValue(pki_contract_address)
     return vf
Ejemplo n.º 5
0
    def __init__(self, project_dir, contract_name, user_addr) -> None:
        super().__init__()
        self.__conn = Runtime.blockchain()
        self.__keystore = {}
        self.__crypto = {}
        self.__prover = Runtime.prover()

        for crypto_params in cfg.all_crypto_params():
            self.__keystore[crypto_params.crypto_name] = Runtime.keystore(
                crypto_params)
            self.__crypto[crypto_params.crypto_name] = Runtime.crypto(
                crypto_params)

        self.__project_dir = project_dir
        self.__contract_name = contract_name

        self.__contract_handle = None
        """Handle which refers to the deployed contract, this is passed to the blockchain interface when e.g. issuing transactions."""

        self.__user_addr = user_addr
        """From address for all transactions which are issued by this ContractSimulator"""

        self.__current_msg: Optional[MsgStruct] = None
        self.__current_block: Optional[BlockStruct] = None
        self.__current_tx: Optional[TxStruct] = None
        """
        Builtin variable (msg, block, tx) values for the current transaction
        """

        self.current_priv_values: Dict[str, Union[int, bool,
                                                  RandomnessValue]] = {}
        """Dictionary which stores the private circuit values (secret inputs) for the current function (no transitivity)"""

        self.all_priv_values: Optional[List[Union[int, bool,
                                                  RandomnessValue]]] = None
        """List which stores all secret circuit inputs for the current transaction in correct order (order of use)"""

        self.current_all_index: Optional[int] = None
        """
        Index which designates where in all_priv_values the secret circuit inputs of the current function should be inserted.
        This is basically private analogue of the start_index parameters which are passed to functions which require verification
        to designate where in the public IO arrays the functions should store/retrieve public circuit inputs/outputs.
        """

        self.is_external: Optional[bool] = None
        """
Ejemplo n.º 6
0
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.is_key():
            backend = pub_input.t.crypto_params.crypto_name
            input_init_stmts.append(
                f'addK("{backend}", "{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)});'
        )

    sec_input_names = [sec_input.name for sec_input in circuit.sec_idfs]
    for crypto_params in cfg.all_crypto_params():
        pk_name = circuit.get_glob_key_name(MeExpr(), crypto_params)
        sk_name = circuit.get_own_secret_key_name(crypto_params)
        if crypto_params.is_symmetric_cipher() and sk_name in sec_input_names:
            assert pk_name in [
                pub_input.name for pub_input in circuit.input_idfs
            ]
            input_init_stmts.append(
                f'setKeyPair("{crypto_params.crypto_name}", "{pk_name}", "{sk_name}");'
            )

    return input_init_stmts
Ejemplo n.º 7
0
    def _connect_libraries(self):
        if not cfg.blockchain_pki_address:
            raise BlockChainError('Must specify pki address in config.')

        lib_addresses = []
        if cfg.external_crypto_lib_names:
            lib_addresses = [addr.strip() for addr in cfg.blockchain_crypto_lib_addresses.split(',')] if cfg.blockchain_crypto_lib_addresses else []
            if len(lib_addresses) != len(cfg.external_crypto_lib_names):
                raise BlockChainError('Must specify all crypto library addresses in config\n'
                                      f'Expected {len(cfg.external_crypto_lib_names)} was {len(lib_addresses)}')

        with cfg.library_compilation_environment():
            with tempfile.TemporaryDirectory() as tmpdir:
                for crypto_params in cfg.all_crypto_params():
                    contract_name = cfg.get_pki_contract_name(crypto_params.crypto_name)
                    pki_sol = save_to_file(tmpdir, f'{contract_name}.sol', library_contracts.get_pki_contract(crypto_params))
                    self._pki_contract = self._verify_contract_integrity(cfg.blockchain_pki_address, pki_sol, contract_name=contract_name)

                verify_sol = save_to_file(tmpdir, 'verify_libs.sol', library_contracts.get_verify_libs_code())
                self._lib_addresses = {}
                for lib, addr in zip(cfg.external_crypto_lib_names, lib_addresses):
                    out = self._verify_contract_integrity(addr, verify_sol, contract_name=lib, is_library=True)
                    self._lib_addresses[lib] = out.address
Ejemplo n.º 8
0
    def connect(self, project_dir: str, contract: str, contract_address: AddressValue, user_address: AddressValue) -> Any:
        """
        Create a handle which can be used to interact with an existing contract on the chain after verifying its integrity.

        Project dir must contain a .zkay file, a manifest.json file as well as a \
        subdirectory *verification_contract_name*\\ _out containing 'proving.key' and 'verification.key' for each verification contract.
        These files are referred to as 'local' files in the following explanation.

        If this function succeeds, it is guaranteed, that:

        * the remote main contract at contract_address, matches the solidity contract obtained by running zkay on the local zkay file
          using the configuration stored in the local manifest
        * the pki contract referenced in the remote main contract matches the correct zkay pki contract
        * the verification contracts referenced in the remote solidity contract were generated by running zkay on a zkay file
          equivalent to local zkay file, with zk-snark keys which match the local keys.
        * the library contract referenced in the verification contracts matches the correct zkay library contract

        This reduces the required trust to the zk-snark setup phase (i.e. you must trust that prover/verification keys
        were generated for the correct circuit), since you can inspect the source code of the local zkay file and check it
        for malicious behavior yourself (and the zkay implementation, which performs the transformation, is open source as well).

        Example Scenarios:

        a) the remote zkay contract is benign (generated by and deployed using zkay):
           -> you will only be able to connect if the local files are equivalent -> correctness is guaranteed
        b) the remote zkay contract was tampered with (any of the .sol files was modified was modified before deployment)
           -> connection will fail, because local zkay compilation will not produce matching evm bytecode
        c) the prover/verification keys were tampered with (they were generated for a different circuit than the one produced by zkay)

           * local keys are genuine -> connection will be refused because the keys don't match what is baked into the remote verification contract
           * same tampered keys locally -> NO GUARANTEES, since the trust assumption is violated

        :param project_dir: directory where the zkay file, manifest and snark keys reside
        :param contract: name of the contract to connect to
        :param contract_address: address of the deployed contract
        :param user_address: account which connects to the contract
        :raise IntegrityError: if the integrity check fails (mismatch between local code and remote contract)
        :return: contract handle for the specified contract
        """
        if not self.is_debug_backend() and cfg.crypto_backend == 'dummy':
            raise BlockChainError('SECURITY ERROR: Dummy encryption can only be used with debug blockchain backends (w3-eth-tester or w3-ganache).')

        zk_print_banner(f'Connect to {contract}@{contract_address}')

        # If not already done, compile zkay file to generate main and verification contracts (but don't generate new prover/verification keys and manifest)
        zk_file = os.path.join(project_dir, 'contract.zkay')
        if not os.path.exists(zk_file):
            raise IntegrityError('No zkay contract found in specified directory')
        verifier_names = []
        if not os.path.exists(os.path.join(project_dir, 'contract.sol')):
            compile_zkay_file(zk_file, project_dir, import_keys=True, verifier_names=verifier_names)
        else:
            with open(zk_file) as f:
                verifier_names = get_verification_contract_names(f.read())

        zk_print(f'Connecting to contract {contract}@{contract_address}')
        contract_on_chain = self._connect(project_dir, contract, contract_address.val)

        pki_verifier_addresses = {}

        # Check integrity of all pki contracts
        self._pki_contract = {}
        for crypto_params in cfg.all_crypto_params():
            contract_name = cfg.get_pki_contract_name(crypto_params)
            pki_address = self._req_state_var(contract_on_chain, f'{contract_name}_inst')
            pki_verifier_addresses[contract_name] = AddressValue(pki_address)
            with cfg.library_compilation_environment():
                contract = self._verify_contract_integrity(pki_address, os.path.join(project_dir, f'{contract_name}.sol'))
                self._pki_contract[crypto_params.crypto_name] = contract

        # Check verifier contract and library integrity
        if verifier_names:
            some_vname = verifier_names[0]

            libraries = [(lib_name, os.path.join(project_dir, ProvingScheme.verify_libs_contract_filename)) for lib_name in cfg.external_crypto_lib_names]
            some_vcontract = self._req_state_var(contract_on_chain, f'{some_vname}_inst')
            libs = self._verify_library_integrity(libraries, some_vcontract, os.path.join(project_dir, f'{some_vname}.sol'))
            self._lib_addresses = libs

            for verifier in verifier_names:
                v_address = self._req_state_var(contract_on_chain, f'{verifier}_inst')
                pki_verifier_addresses[verifier] = AddressValue(v_address)
                vcontract = self._verify_contract_integrity(v_address, os.path.join(project_dir, f'{verifier}.sol'), libraries=libs)

                # Verify prover key
                expected_hash = self._req_state_var(vcontract, cfg.prover_key_hash_name)
                from zkay.transaction.runtime import Runtime
                actual_hash = Runtime.prover().get_prover_key_hash(os.path.join(project_dir, cfg.get_circuit_output_dir_name(verifier)))
                if expected_hash != actual_hash:
                    raise IntegrityError(f'Prover key hash in deployed verification contract does not match local prover key file for "{verifier}"')

        # Check zkay contract integrity
        self._verify_zkay_contract_integrity(contract_on_chain.address, project_dir, pki_verifier_addresses)

        with success_print():
            zk_print(f'OK: Bytecode on blockchain matches local zkay contract')
        zk_print(f'Connection from account 0x{user_address} established\n')

        return contract_on_chain
Ejemplo n.º 9
0
    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)
Ejemplo n.º 10
0
def main():
    # parse arguments
    a = parse_arguments()

    from zkay.config_version import Versions

    if a.cmd == 'version':
        print(Versions.ZKAY_VERSION)
        return

    if a.cmd == 'update-solc':
        try:
            import solcx
            solcx.install_solc_pragma(
                Versions.ZKAY_SOLC_VERSION_COMPATIBILITY.expression)
        except Exception as e:
            print(f'ERROR: Error while updating solc\n{e}')
        return

    from pathlib import Path

    import zkay.zkay_frontend as frontend
    from zkay import my_logging
    from zkay.config import cfg
    from zkay.utils.helpers import read_file, save_to_file
    from zkay.errors.exceptions import ZkayCompilerError
    from zkay.my_logging.log_context import log_context
    from zkay.utils.progress_printer import fail_print, success_print
    from zkay.zkay_ast.process_ast import get_processed_ast, get_parsed_ast_and_fake_code

    # Load configuration files
    try:
        cfg.load_configuration_from_disk(a.config_file)
    except Exception as e:
        with fail_print():
            print(f"ERROR: Failed to load configuration files\n{e}")
            exit(42)

    # Support for overriding any user config setting via command line
    # The evaluation order for configuration loading is:
    # Default values in config.py -> Site config.json -> user config.json -> local config.json -> cmdline arguments
    # Settings defined at a later stage override setting values defined at an earlier stage
    override_dict = {}
    for name in vars(UserConfig):
        if name[0] != '_' and hasattr(a, name):
            val = getattr(a, name)
            if val is not None:
                override_dict[name] = val
    cfg.override_defaults(override_dict)

    if a.cmd in ['deploy-pki', 'deploy-crypto-libs']:
        import tempfile
        from zkay.compiler.privacy import library_contracts
        from zkay.transaction.runtime import Runtime
        with tempfile.TemporaryDirectory() as tmpdir:
            try:
                with cfg.library_compilation_environment():
                    if a.cmd == 'deploy-pki':
                        for crypto_params in cfg.all_crypto_params():
                            pki_contract_code = library_contracts.get_pki_contract(
                                crypto_params)
                            pki_contract_name = cfg.get_pki_contract_name(
                                crypto_params)
                            file = save_to_file(tmpdir,
                                                f'{pki_contract_name}.sol',
                                                pki_contract_code)
                            addr = Runtime.blockchain(
                            ).deploy_solidity_contract(file, pki_contract_name,
                                                       a.account)
                            print(
                                f'Deployed pki contract for crypto backend "{crypto_params.crypto_name}" at: {addr}'
                            )
                    else:
                        if not cfg.external_crypto_lib_names:
                            print(
                                'Current proving scheme does not require library deployment'
                            )
                        else:
                            file = save_to_file(
                                tmpdir, 'verify_libs.sol',
                                library_contracts.get_verify_libs_code())
                            for lib in cfg.external_crypto_lib_names:
                                addr = Runtime.blockchain(
                                ).deploy_solidity_contract(
                                    file, lib, a.account)
                                print(
                                    f'Deployed crypto library {lib} at: {addr}'
                                )
            except Exception as e:
                with fail_print():
                    print(f"ERROR: Deployment failed\n{e}")
    else:
        # Solc version override
        if hasattr(a, 'solc_version') and a.solc_version is not None:
            try:
                cfg.override_solc(a.solc_version)
            except ValueError as e:
                with fail_print():
                    print(f'Error: {e}')
                exit(10)
        print(f'Using solc version {Versions.SOLC_VERSION}')

        input_path = Path(a.input)
        if not input_path.exists():
            with fail_print():
                print(f'Error: input file \'{input_path}\' does not exist')
            exit(1)

        if a.cmd == 'check':
            # only type-check
            print(f'Type checking file {input_path.name}:')

            code = read_file(str(input_path))
            try:
                get_processed_ast(code)
            except ZkayCompilerError as e:
                with fail_print():
                    print(f'{e}')
                exit(3)
        elif a.cmd == 'solify':
            was_unit_test = cfg.is_unit_test
            cfg._is_unit_test = True  # Suppress other output
            try:
                _, fake_code = get_parsed_ast_and_fake_code(
                    read_file(str(input_path)))
                print(fake_code)
            except ZkayCompilerError as e:
                with fail_print():
                    print(f'{e}')
                exit(3)
            finally:
                cfg._is_unit_test = was_unit_test
            exit(0)
        elif a.cmd == 'compile':
            # create output directory
            output_dir = Path(a.output).absolute()
            if not output_dir.exists():
                os.makedirs(output_dir)
            elif not output_dir.is_dir():
                with fail_print():
                    print(f'Error: \'{output_dir}\' is not a directory')
                exit(2)

            # Enable logging
            if a.log:
                log_file = my_logging.get_log_file(filename='compile',
                                                   include_timestamp=False,
                                                   label=None)
                my_logging.prepare_logger(log_file)

            # only type-check
            print(f'Compiling file {input_path.name}:')

            # compile
            with log_context(os.path.basename(a.input)):
                try:
                    frontend.compile_zkay_file(str(input_path),
                                               str(output_dir))
                except ZkayCompilerError as e:
                    with fail_print():
                        print(f'{e}')
                    exit(3)
        elif a.cmd == 'import':
            # create output directory
            output_dir = Path(a.output).absolute()
            if output_dir.exists():
                with fail_print():
                    print(f'Error: \'{output_dir}\' already exists')
                exit(2)

            try:
                frontend.extract_zkay_package(str(input_path), str(output_dir))
            except ZkayCompilerError as e:
                with fail_print():
                    print(
                        f"ERROR while compiling unpacked zkay contract.\n{e}")
                exit(3)
            except Exception as e:
                with fail_print():
                    print(f"ERROR while unpacking zkay contract\n{e}")
                exit(5)
        elif a.cmd == 'export':
            output_filename = Path(a.output).absolute()
            os.makedirs(output_filename.parent, exist_ok=True)
            try:
                frontend.package_zkay_contract(str(input_path),
                                               str(output_filename))
            except Exception as e:
                with fail_print():
                    print(f"ERROR while exporting zkay contract\n{e}")
                exit(4)
        elif a.cmd in ['run', 'deploy', 'connect']:
            from enum import IntEnum
            from zkay.transaction.offchain import ContractSimulator

            def echo_only_simple_expressions(e):
                if isinstance(e, (bool, int, str, list, tuple, IntEnum)):
                    print(e)

            # Enable logging
            if a.log:
                log_file = my_logging.get_log_file(
                    filename=f'transactions_{input_path.name}',
                    include_timestamp=True,
                    label=None)
                my_logging.prepare_logger(log_file)

            contract_dir = str(input_path.absolute())
            frontend.use_configuration_from_manifest(contract_dir)
            if a.account is not None:
                me = a.account
            else:
                me = ContractSimulator.default_address()
                if me is not None:
                    me = me.val

            import code
            import sys
            if a.cmd == 'run':
                # Dynamically load module and replace globals with module globals
                contract_mod = frontend.load_transaction_interface_from_directory(
                    contract_dir)
                contract_mod.me = me
                sys.displayhook = echo_only_simple_expressions
                code.interact(local=contract_mod.__dict__)
            else:
                cmod = frontend.load_transaction_interface_from_directory(
                    contract_dir)
                c = frontend.load_contract_transaction_interface_from_module(
                    cmod)
                if a.cmd == 'deploy':
                    from ast import literal_eval
                    cargs = a.constructor_args
                    args = []
                    for arg in cargs:
                        try:
                            val = literal_eval(arg)
                        except Exception:
                            val = arg
                        args.append(val)
                    try:
                        c.deploy(*args, user=me, project_dir=contract_dir)
                    except (ValueError, TypeError) as e:
                        with fail_print():
                            print(
                                f'ERROR invalid arguments.\n{e}\n\nExpected contructor signature: ',
                                end='')
                            if hasattr(c, 'constructor'):
                                from inspect import signature
                                sig = str(signature(c.constructor))
                                sig = sig[5:] if not sig[5:].startswith(
                                    ",") else sig[7:]  # without self
                                print(f'({sig}')
                            else:
                                print('()')
                            exit(11)
                    except Exception as e:
                        with fail_print():
                            print(f'ERROR: failed to deploy contract\n{e}')
                            exit(12)
                elif a.cmd == 'connect':
                    try:
                        c_inst = c.connect(address=a.address,
                                           user=me,
                                           project_dir=contract_dir)
                    except Exception as e:
                        with fail_print():
                            print(f'ERROR: failed to connect to contract\n{e}')
                            exit(13)

                    # Open interactive shell in context of contract object
                    import inspect
                    contract_scope = {name: getattr(c_inst, name) for name in dir(c_inst)
                                      if inspect.isclass(getattr(c_inst, name)) \
                                         or (name != 'constructor' and hasattr(getattr(c_inst, name), '_can_be_external')
                                             and getattr(c_inst, name)._can_be_external)
                                         or name in ['state', 'api']}
                    contract_scope['me'] = me
                    contract_scope['help'] = lambda o=None: help(
                        o
                    ) if o is not None else ContractSimulator.reduced_help(c)
                    sys.displayhook = echo_only_simple_expressions
                    code.interact(local=contract_scope)
                else:
                    raise ValueError(f'unexpected command {a.cmd}')
            exit(0)
        else:
            raise NotImplementedError(a.cmd)

        with success_print():
            print("Finished successfully")