示例#1
0
def _collect_package_contents(contract_dir: str, check_all_files: bool) -> List[str]:
    """
    Return list of relative paths of all files which should be part of the package for the contract in contract_dir.

    Raises an exception if contract.zkay, manifest.json or any of the files required by contract.zkay is missing.

    :param contract_dir: path to directory containing manifest and zkay file
    :param check_all_files: if true, checks whether all expected files are present
    :raise FileNotFoundError: if any of the expected files is not present
    :return: list of relative paths (relative to contract_dir)
    """

    zkay_filename = os.path.join(contract_dir, 'contract.zkay')
    if not os.path.exists(zkay_filename):
        raise FileNotFoundError('contract.zkay not found in package')

    manifest_filename = os.path.join(contract_dir, 'manifest.json')
    if not os.path.exists(manifest_filename):
        raise FileNotFoundError('manifest.json not found in package')
    manifest = Manifest.load(contract_dir)

    files = ['contract.zkay', 'manifest.json']
    with open(zkay_filename) as f:
        verifier_names = get_verification_contract_names(f.read())
    with Manifest.with_manifest_config(manifest):
        gen_cls = generator_classes[cfg.snark_backend]
        files += [os.path.join(cfg.get_circuit_output_dir_name(v), k)
                  for k in gen_cls.get_vk_and_pk_filenames() for v in verifier_names]

    if check_all_files:
        for f in files:
            path = os.path.join(contract_dir, f)
            if not os.path.exists(path) or not os.path.isfile(path):
                raise FileNotFoundError(f)
    return files
示例#2
0
    def _deploy(self, project_dir: str, sender: Union[bytes, str], contract: str, *actual_args, wei_amount: Optional[int] = None) -> Any:
        with open(os.path.join(project_dir, 'contract.zkay')) as f:
            verifier_names = get_verification_contract_names(f.read())

        # Deploy verification contracts if not already done
        external_contract_addresses =  self._deploy_dependencies(sender, project_dir, verifier_names)
        with self.__hardcoded_external_contracts_ctx(project_dir, external_contract_addresses) as filename:
            cout = self.compile_contract(filename, contract, cwd=project_dir)
        with log_context('constructor'):
            with log_context(f'{contract}'):
                handle = self._deploy_contract(sender, cout, *actual_args, wei_amount=wei_amount)
        zk_print(f'Deployed contract "{contract}" at address "{handle.address}"')
        return handle
示例#3
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
示例#4
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