Beispiel #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('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}"')
Beispiel #2
0
    def visitSourceUnit(self, ast: SourceUnit):
        # Figure out which crypto backends were used
        UsedHomomorphismsVisitor().visit(ast)

        for crypto_params in ast.used_crypto_backends:
            self.import_contract(cfg.get_pki_contract_name(crypto_params), ast)

        for c in ast.contracts:
            self.transform_contract(ast, c)

        return ast
Beispiel #3
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
Beispiel #4
0
    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)]))
Beispiel #5
0
    def include_verification_contracts(self, su: SourceUnit, c: ContractDefinition) -> List[StateVariableDeclaration]:
        """
        Import all verification contracts for 'c' into 'su' and create state variable declarations for all of them + the pki contract.

        :param su: [SIDE EFFECT] source unit into which contracts should be imported
        :param c: contract for which verification contracts should be imported
        :return: list of all constant state variable declarations for the pki contract + all the verification contracts
        """
        contract_var_decls = []
        for crypto_params in c.used_crypto_backends:
            contract_name = cfg.get_pki_contract_name(crypto_params)
            contract_var_decls.append(self.create_contract_variable(contract_name))

        for f in c.constructor_definitions + c.function_definitions:
            if f.requires_verification_when_external and f.has_side_effects:
                name = cfg.get_verification_contract_name(c.idf.name, f.name)
                self.import_contract(name, su, self.circuits[f])
                contract_var_decls.append(self.create_contract_variable(name))

        return contract_var_decls
Beispiel #6
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
Beispiel #7
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
Beispiel #8
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")