Пример #1
0
def process_ast(ast,
                parents=True,
                link_identifiers=True,
                check_return=True,
                alias_analysis=True,
                type_check=True):
    with print_step("Preprocessing AST"):
        if parents:
            set_parents(ast)
        if link_identifiers:
            try:
                link(ast)
            except UnknownIdentifierException as e:
                raise PreprocessAstException(f'\n\nSYMBOL ERROR: {e}')
        try:
            if check_return:
                r(ast)
            if alias_analysis:
                a(ast)
            call_graph_analysis(ast)
            compute_modified_sets(ast)
            check_for_undefined_behavior_due_to_eval_order(ast)
        except AstException as e:
            raise AnalysisException(f'\n\nANALYSIS ERROR: {e}')
    if type_check:
        with print_step("Zkay type checking"):
            try:
                t(ast)
                check_circuit_compliance(ast)
                detect_hybrid_functions(ast)
                check_loops(ast)
            except (TypeMismatchException, TypeException, RequireException,
                    ReclassifyException) as e:
                raise TypeCheckException(f'\n\nCOMPILER ERROR: {e}')
Пример #2
0
def get_parsed_ast_and_fake_code(code, solc_check=True) -> Tuple[AST, str]:
    with print_step("Parsing"):
        try:
            ast = build_ast(code)
        except SyntaxException as e:
            raise ZkaySyntaxError(f'\n\nSYNTAX ERROR: {e}')

    from zkay.compiler.solidity.fake_solidity_generator import fake_solidity_code
    fake_code = fake_solidity_code(str(code))
    if solc_check:
        # Solc type checking
        with print_step("Type checking with solc"):
            try:
                check_for_zkay_solc_errors(code, fake_code)
            except SolcException as e:
                raise ZkayCompilerError(f'{e}')
    return ast, fake_code
Пример #3
0
def extract_zkay_package(zkp_filename: str, output_dir: str):
    """
    Unpack and compile a zkay contract.

    :param zkp_filename: path to the packaged contract
    :param output_dir: directory where to unpack and compile the contract
    :raise Exception: if import fails
    """
    os.makedirs(output_dir)
    try:
        with zipfile.ZipFile(zkp_filename) as zkp:
            with print_step('Checking zip file integrity'):
                if zkp.testzip() is not None:
                    raise ValueError('Corrupt archive')

            with print_step('Checking for correct file structure'):
                zkp.extract('contract.zkay', output_dir)
                zkp.extract('manifest.json', output_dir)
                expected_files = sorted(_collect_package_contents(output_dir, False))
                contained_files = sorted([d.filename for d in zkp.infolist() if not d.is_dir()])
                if expected_files != contained_files:
                    raise ValueError(f'Package is invalid, does not match expected contents')

            with print_step('Extracting archive'):
                zkp.extractall(output_dir)

        # Compile extracted contract
        zkay_filename = os.path.join(output_dir, 'contract.zkay')
        manifest = Manifest.load(output_dir)
        with Manifest.with_manifest_config(manifest):
            compile_zkay_file(zkay_filename, output_dir, import_keys=True)
    except Exception as e:
        # If there was an exception, the archive is not safe -> remove extracted contents
        print(f'Package {zkp_filename} is either corrupt or incompatible with this zkay version.')
        shutil.rmtree(output_dir)
        raise e
Пример #4
0
def package_zkay_contract(zkay_output_dir: str, output_filename: str):
    """Package zkay contract for distribution."""
    if not output_filename.endswith('.zkp'):
        output_filename += '.zkp'

    with print_step('Packaging for distribution'):
        files = _collect_package_contents(zkay_output_dir, True)

        with tempfile.TemporaryDirectory() as tmpdir:
            for file in files:
                src = os.path.join(zkay_output_dir, file)
                dest = os.path.join(tmpdir, file)
                os.makedirs(os.path.dirname(dest), exist_ok=True)
                shutil.copyfile(src, dest)
            shutil.make_archive(without_extension(output_filename), 'zip', tmpdir)
        os.rename(f'{without_extension(output_filename)}.zip', output_filename)
Пример #5
0
    def generate_circuits(self, *, import_keys: bool):
        """
        Generate circuit code and verification contracts based on the provided circuits and proving scheme.

        :param import_keys: if false, new verification and prover keys will be generated, otherwise key files for all verifiers
                            are expected to be already present in the respective output directories
        """
        # Generate proof circuit code

        # Compile circuits
        c_count = len(self.circuits_to_prove)
        zk_print(f'Compiling {c_count} circuits...')

        gen_circs = functools.partial(self._generate_zkcircuit, import_keys)
        with time_measure('circuit_compilation', True):
            if cfg.is_unit_test:
                modified = list(map(gen_circs, self.circuits_to_prove))
            else:
                with Pool(processes=self.p_count) as pool:
                    modified = pool.map(gen_circs, self.circuits_to_prove)

        if import_keys:
            for path in self.get_all_key_paths():
                if not os.path.exists(path):
                    raise RuntimeError("Zkay contract import failed: Missing keys")
        else:
            modified_circuits_to_prove = [circ for t, circ in zip(modified, self.circuits_to_prove)
                                          if t or not all(map(os.path.exists, self._get_vk_and_pk_paths(circ)))]

            # Generate keys in parallel
            zk_print(f'Generating keys for {c_count} circuits...')
            with time_measure('key_generation', True):
                if self.parallel_keygen and not cfg.is_unit_test:
                    counter = Value('i', 0)
                    with Pool(processes=self.p_count, initializer=self.__init_worker, initargs=(counter, c_count,)) as pool:
                        pool.map(self._generate_keys_par, modified_circuits_to_prove)
                else:
                    for circ in modified_circuits_to_prove:
                        self._generate_keys(circ)

        with print_step('Write verification contracts'):
            for circuit in self.circuits_to_prove:
                vk = self._parse_verification_key(circuit)
                pk_hash = self._get_prover_key_hash(circuit)
                with open(os.path.join(self.output_dir, circuit.verifier_contract_filename), 'w') as f:
                    primary_inputs = self._get_primary_inputs(circuit)
                    f.write(self.proving_scheme.generate_verification_contract(vk, circuit, primary_inputs, pk_hash))
Пример #6
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