Example #1
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('transaction', 'deploy_pki'):
                    pki_sol = save_to_file(
                        tmpdir, f'{cfg.pki_contract_name}.sol',
                        library_contracts.get_pki_contract())
                    self._pki_contract = self._deploy_contract(
                        sender,
                        self.compile_contract(pki_sol, cfg.pki_contract_name))
                    zk_print(
                        f'Deployed pki contract at address "{self.pki_contract.address}"'
                    )

                with log_context('transaction', '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}"'
                        )
Example #2
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
Example #3
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':
                        file = save_to_file(
                            tmpdir, f'{cfg.pki_contract_name}.sol',
                            library_contracts.get_pki_contract())
                        addr = Runtime.blockchain().deploy_solidity_contract(
                            file, cfg.pki_contract_name, a.account)
                        print(f'Deployed pki contract 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('inputfile', 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")
Example #4
0
def compile_zkay(code: str, output_dir: str, import_keys: bool = False, **kwargs) -> Tuple[CircuitGenerator, str]:
    """
    Parse, type-check and compile the given zkay code.

    Note: If a SolcException is raised, this indicates a bug in zkay
          (i.e. zkay produced solidity code which doesn't compile, without raising a ZkayCompilerError)

    :param code: zkay code to compile
    :param output_dir: path to a directory where the compilation output should be generated
    :param import_keys: | if false, zk-snark of all modified circuits will be generated during compilation
                        | if true, zk-snark keys for all circuits are expected to be already present in the output directory, \
                          and the compilation will use the provided keys to generate the verification contracts
                        | This option is mostly used internally when connecting to a zkay contract provided by a 3rd-party
    :raise ZkayCompilerError: if any compilation stage fails
    :raise RuntimeError: if import_keys is True and zkay file, manifest file or any of the key files is missing
    """

    # Copy zkay code to output
    zkay_filename = 'contract.zkay'
    if import_keys and not os.path.exists(os.path.join(output_dir, zkay_filename)):
        raise RuntimeError('Zkay file is expected to already be in the output directory when importing keys')
    elif not import_keys:
        _dump_to_output(code, output_dir, zkay_filename)

    # Type checking
    zkay_ast = get_processed_ast(code)

    # Contract transformation
    with print_step("Transforming zkay -> public contract"):
        ast, circuits = transform_ast(deepcopy(zkay_ast))

    # Dump libraries
    with print_step("Write library contract files"):
        with cfg.library_compilation_environment():
            for crypto_params in ast.used_crypto_backends:
                # Write pki contract
                pki_contract_code = library_contracts.get_pki_contract(crypto_params)
                pki_contract_file = f'{cfg.get_pki_contract_name(crypto_params)}.sol'
                _dump_to_output(pki_contract_code, output_dir, pki_contract_file, dryrun_solc=True)

            # Write library contract
            _dump_to_output(library_contracts.get_verify_libs_code(), output_dir, ProvingScheme.verify_libs_contract_filename, dryrun_solc=True)

    # Write public contract file
    with print_step('Write public solidity code'):
        output_filename = 'contract.sol'
        solidity_code_output = _dump_to_output(to_solidity(ast), output_dir, output_filename)

    # Get all circuit helpers for the transformed contract
    circuits: List[CircuitHelper] = list(circuits.values())

    # Generate offchain simulation code (transforms transactions, interface to deploy and access the zkay contract)
    offchain_simulation_code = PythonOffchainVisitor(circuits).visit(ast)
    _dump_to_output(offchain_simulation_code, output_dir, 'contract.py')

    # Instantiate proving scheme and circuit generator
    ps = proving_scheme_classes[cfg.proving_scheme]()
    cg = generator_classes[cfg.snark_backend](circuits, ps, output_dir)

    if 'verifier_names' in kwargs:
        assert isinstance(kwargs['verifier_names'], list)
        verifier_names = get_verification_contract_names(zkay_ast)
        assert sorted(verifier_names) == sorted([cc.verifier_contract_type.code() for cc in cg.circuits_to_prove])
        kwargs['verifier_names'][:] = verifier_names[:]

    # Generate manifest
    if not import_keys:
        with print_step("Writing manifest file"):
            # Set crypto backends for unused homomorphisms to None
            for hom in Homomorphism:
                if hom not in ast.used_homomorphisms:
                    cfg.set_crypto_backend(hom, None)

            manifest = {
                Manifest.zkay_version: cfg.zkay_version,
                Manifest.solc_version: cfg.solc_version,
                Manifest.zkay_options: cfg.export_compiler_settings(),
            }
            _dump_to_output(json.dumps(manifest), output_dir, 'manifest.json')
    elif not os.path.exists(os.path.join(output_dir, 'manifest.json')):
        raise RuntimeError('Zkay contract import failed: Manifest file is missing')

    # Generate circuits and corresponding verification contracts
    cg.generate_circuits(import_keys=import_keys)

    # Check that all verification contracts and the main contract compile
    main_solidity_files = cg.get_verification_contract_filenames() + [os.path.join(output_dir, output_filename)]
    for f in main_solidity_files:
        check_compilation(f, show_errors=False)

    return cg, solidity_code_output