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}')
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
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
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)
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))
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