Example #1
0
    def generate_proof(self, project_dir: str, contract: str, function: str, priv_values: List, in_vals: List, out_vals: List[Union[int, CipherValue]]) -> List[int]:
        """
        Generate a NIZK-proof using the provided circuit for the given arguments.

        Note: circuit arguments must be in the same order as they are declared inside the circuit. (i.e. in execution order)

        :param project_dir: directory where the manifest and the prover keys are located
        :param contract: contract of which the function which requires verification is part of
        :param function: the contract member function for which a proof needs to be generated
        :param priv_values: private/auxiliary circuit inputs in correct order
        :param in_vals: public circuit inputs in correct order
        :param out_vals: public circuit outputs in correct order
        :raise ProofGenerationError: if proof generation fails
        :return: the proof, serialized into an uint256 array
        """
        for i in range(len(priv_values)):
            arg = priv_values[i]
            assert not isinstance(arg, Value) or isinstance(arg, (RandomnessValue, AddressValue))
            if isinstance(arg, AddressValue):
                priv_values[i] = int.from_bytes(arg.val, byteorder='big')

        zk_print(f'Generating proof for {contract}.{function}')
        zk_print(f'[priv: {Value.collection_to_string(priv_values)}] '
                 f'[in: {Value.collection_to_string(in_vals)}] [out: {Value.collection_to_string(out_vals)}]', verbosity_level=2)

        priv_values, in_vals, out_vals = Value.unwrap_values(Value.flatten(priv_values)), Value.unwrap_values(in_vals), Value.unwrap_values(out_vals)

        # Check for overflows
        for arg in priv_values + in_vals + out_vals:
            assert int(arg) < bn128_scalar_field, 'argument overflow'

        with time_measure(f'generate_proof', True):
            verify_dir = cfg.get_circuit_output_dir_name(cfg.get_verification_contract_name(contract, function))
            return self._generate_proof(os.path.join(project_dir, verify_dir), priv_values, in_vals, out_vals)
Example #2
0
    def enc(self, plain: Union[int, AddressValue], my_addr: AddressValue, target_addr: AddressValue) -> Tuple[CipherValue, Optional[RandomnessValue]]:
        """
        Encrypt plain for receiver with target_addr.

        :param plain: plain text to encrypt
        :param my_addr: address of the sender who encrypts
        :param target_addr: address of the receiver for whom to encrypt
        :return: if symmetric -> (iv_cipher, None), if asymmetric (cipher, randomness which was used to encrypt plain)
        """
        if isinstance(plain, AddressValue):
            plain = int.from_bytes(plain.val, byteorder='big')
        assert not isinstance(plain, Value), f"Tried to encrypt value of type {type(plain).__name__}"
        assert isinstance(my_addr, AddressValue) and isinstance(target_addr, AddressValue)
        assert int(plain) < bn128_scalar_field, f"Integer overflow, plaintext is >= field prime"
        zk_print(f'Encrypting value {plain} for destination "{target_addr}"', verbosity_level=2)

        sk = self.keystore.sk(my_addr).val
        raw_pk = self.keystore.getPk(target_addr)
        if self.params.is_symmetric_cipher():
            assert len(raw_pk) == 1
            pk = raw_pk[0]
        else:
            pk = self.deserialize_pk(raw_pk[:])
        while True:
            # Retry until cipher text is not 0
            cipher, rnd = self._enc(int(plain), sk, pk)
            cipher = CipherValue(cipher, params=self.params)
            rnd = RandomnessValue(rnd, params=self.params) if rnd is not None else None
            if cipher != CipherValue(params=self.params):
                break

        return cipher, rnd
Example #3
0
    def _verify_contract_integrity(self, address: Union[bytes, str], sol_filename: str, *,
                                   libraries: Dict = None, contract_name: str = None, is_library: bool = False,
                                   cwd=None) -> Any:
        if isinstance(address, bytes):
            address = self.w3.toChecksumAddress(address)

        if contract_name is None:
            contract_name = get_contract_names(sol_filename)[0]
        actual_byte_code = self.__normalized_hex(self.w3.eth.getCode(address))
        if not actual_byte_code:
            raise IntegrityError(f'Expected contract {contract_name} is not deployed at address {address}')

        cout = self.compile_contract(sol_filename, contract_name, libs=libraries, cwd=cwd)
        expected_byte_code = self.__normalized_hex(cout['deployed_bin'])

        if is_library:
            # https://github.com/ethereum/solidity/issues/7101
            expected_byte_code = expected_byte_code[:2] + self.__normalized_hex(address) + expected_byte_code[42:]

        if actual_byte_code != expected_byte_code:
            raise IntegrityError(f'Deployed contract at address {address} does not match local contract {sol_filename}')
        zk_print(f'Contract@{address} matches {sol_filename[sol_filename.rfind("/") + 1:]}:{contract_name}')

        return self.w3.eth.contract(
            address=address, abi=cout['abi']
        )
Example #4
0
    def deploy(self, project_dir: str, sender: AddressValue, contract: str, actual_args: List, should_encrypt: List[bool], wei_amount: Optional[int] = None) -> Any:
        """
        Issue a deployment transaction which constructs the specified contract with the provided constructor arguments on the chain.

        **WARNING: THIS ISSUES A CRYPTO CURRENCY TRANSACTION (GAS COST)**

        :param project_dir: directory where the zkay file, manifest and snark keys reside
        :param sender: creator address, its eth private key must be hosted in the eth node to which the backend connects.
        :param contract: name of the contract to instantiate
        :param actual_args: the constructor argument values
        :param should_encrypt: a list which contains a boolean value for each argument, which should be true if the corresponding
                               parameter expects an encrypted/private value (this is only used for a last sanity-check)
        :param wei_amount: how much money to send along with the constructor transaction (only for payable constructors)
        :raise BlockChainError: if there is an error in the backend
        :raise TransactionFailedException: if the deployment transaction failed
        :return: handle for the newly created 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'Deploy {contract}')

        self.__check_args(actual_args, should_encrypt)
        zk_print(f'Deploying contract {contract}{Value.collection_to_string(actual_args)}')
        ret = self._deploy(project_dir, sender.val, contract, *Value.unwrap_values(actual_args), wei_amount=wei_amount)
        zk_print()
        return ret
Example #5
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 #6
0
def get_dlog(x: int, y: int):
    zk_print(f'Fetching discrete log for {x}, {y}...', verbosity_level=2)
    xb = to_le_32_hex_bytes(x)
    yb = to_le_32_hex_bytes(y)
    zk_print(f'Running babygiant with arguments {xb}, {yb}...',
             verbosity_level=2)

    return int(babygiant.compute_dlog(xb, yb))
Example #7
0
 def get_special_variables(
         self,
         sender: AddressValue,
         wei_amount: int = 0) -> Tuple[MsgStruct, BlockStruct, TxStruct]:
     block = self.w3.eth.getBlock('pending')
     zk_print(f'Current block timestamp: {block["timestamp"]}')
     return MsgStruct(sender, wei_amount), \
            BlockStruct(AddressValue(self.w3.eth.coinbase), block['difficulty'], block['gasLimit'], block['number'], block['timestamp']),\
            TxStruct(self.w3.eth.gasPrice, sender)
Example #8
0
    def _generate_zkcircuit(self, import_keys: bool,
                            circuit: CircuitHelper) -> bool:
        # Create output directory
        output_dir = self._get_circuit_output_dir(circuit)
        if not os.path.exists(output_dir):
            os.mkdir(output_dir)

        # Generate java code for all functions which are transitively called by the fct corresponding to this circuit
        # (outside private expressions)
        fdefs = []
        for fct in list(circuit.transitively_called_functions.keys()):
            target_circuit = self.circuits[fct]
            body_stmts = JsnarkVisitor(target_circuit.phi).visitCircuit()

            body = '\n'.join([f'stepIn("{fct.name}");'] +
                             add_function_circuit_arguments(target_circuit) +
                             [''] + [stmt
                                     for stmt in body_stmts] + ['stepOut();'])
            fdef = f'private void _{fct.name}() {{\n' + indent(body) + '\n}'
            fdefs.append(f'{fdef}')

        # Generate java code for the function corresponding to this circuit
        input_init_stmts = add_function_circuit_arguments(circuit)
        constraints = JsnarkVisitor(circuit.phi).visitCircuit()

        # Inject the function definitions into the java template
        code = jsnark.get_jsnark_circuit_class_str(
            circuit, fdefs, input_init_stmts + [''] + constraints)

        # Compute combined hash of the current jsnark interface jar and of the contents of the java file
        hashfile = os.path.join(output_dir,
                                f'{cfg.jsnark_circuit_classname}.hash')
        digest = hash_string((jsnark.circuit_builder_jar_hash + code +
                              cfg.proving_scheme).encode('utf-8')).hex()
        if os.path.exists(hashfile):
            with open(hashfile, 'r') as f:
                oldhash = f.read()
        else:
            oldhash = ''

        # Invoke jsnark compilation if either the jsnark-wrapper or the current circuit was modified (based on hash comparison)
        if oldhash != digest or not os.path.exists(
                os.path.join(output_dir, 'circuit.arith')):
            if not import_keys:
                # Remove old keys
                for f in self._get_vk_and_pk_paths(circuit):
                    if os.path.exists(f):
                        os.remove(f)
            jsnark.compile_circuit(output_dir, code)
            with open(hashfile, 'w') as f:
                f.write(digest)
            return True
        else:
            zk_print(
                f'Circuit \'{circuit.get_verification_contract_name()}\' not modified, skipping compilation'
            )
            return False
Example #9
0
File: timer.py Project: nibau/zkay
def time_measure(key, should_print=False):
    start = time.time()
    yield
    end = time.time()
    elapsed = end - start

    if should_print:
        zk_print(f"Took {elapsed} s")
    my_logging.data(key, elapsed)
Example #10
0
    def req_public_key(self, address: AddressValue, crypto_params: CryptoParams) -> PublicKeyValue:
        """
        Request the public key for the designated address from the PKI contract.

        :param address: Address for which to request public key
        :raise BlockChainError: if request fails
        :return: the public key
        """
        assert isinstance(address, AddressValue)
        zk_print(f'Requesting public key for address "{address}"', verbosity_level=2)
        return self._req_public_key(address.val, crypto_params)
Example #11
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
Example #12
0
    def req_state_var(self, contract_handle, name: str, *indices) -> Union[bool, int, str, bytes]:
        """
        Request the contract state variable value name[indices[0]][indices[1]][...] from the chain.

        :param contract_handle: contract from which to read state
        :param name: name of the state variable
        :param indices: if the request is for an (nested) array/map index value, the values of all index keys.
        :raise BlockChainError: if request fails
        :return: The value
        """
        assert contract_handle is not None
        zk_print(f'Requesting state variable "{name}"', verbosity_level=2)
        val = self._req_state_var(contract_handle, name, *Value.unwrap_values(list(indices)))
        zk_print(f'Got value {val} for state variable "{name}"', verbosity_level=2)
        return val
Example #13
0
    def announce_public_key(self, sender: AddressValue, pk: PublicKeyValue, crypto_params: CryptoParams) -> Any:
        """
        Announce a public key to the PKI

        **WARNING: THIS ISSUES A CRYPTO CURRENCY TRANSACTION (GAS COST)**

        :param sender: public key owner, its eth private key must be hosted in the eth node to which the backend connects.
        :param pk: the public key to announce
        :raise BlockChainError: if there is an error in the backend
        :raise TransactionFailedException: if the announcement transaction failed
        :return: backend-specific transaction receipt
        """
        assert isinstance(sender, AddressValue)
        assert isinstance(pk, PublicKeyValue)
        zk_print(f'Announcing public key "{pk}" for address "{sender}"')
        return self._announce_public_key(sender.val, pk[:], crypto_params)
Example #14
0
    def call(self, contract_handle, sender: AddressValue, name: str, *args) -> Union[bool, int, str, bytes, List]:
        """
        Call the specified pure/view function in the given contract with the provided arguments.

        :param contract_handle: the contract in which the function resides
        :param sender: sender address, its eth private key must be hosted in the eth node to which the backend connects.
        :param name: name of the function to call
        :param args: argument values
        :raise BlockChainError: if request fails
        :return: function return value (single value if one return value, list if multiple return values)
        """
        assert contract_handle is not None
        zk_print(f'Calling contract function {name}{Value.collection_to_string(args)}', verbosity_level=2)
        val = self._call(contract_handle, sender.val, name, *Value.unwrap_values(list(args)))
        zk_print(f'Got return value {val}', verbosity_level=2)
        return val
Example #15
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))
Example #16
0
    def _transact(self, contract_handle, sender: Union[bytes, str], function: str, *actual_params, wei_amount: Optional[int] = None) -> Any:
        try:
            fct = contract_handle.constructor if function == 'constructor' else contract_handle.functions[function]
            gas_amount = self._gas_heuristic(sender, fct(*actual_params))
            tx = {'from': sender, 'gas': gas_amount}
            if wei_amount:
                tx['value'] = wei_amount
            tx_hash = fct(*actual_params).transact(tx)
            tx_receipt = self.w3.eth.waitForTransactionReceipt(tx_hash)
        except Exception as e:
            raise BlockChainError(e.args)

        if tx_receipt['status'] == 0:
            raise TransactionFailedException("Transaction failed")
        gas = tx_receipt['gasUsed']
        zk_print(f"Consumed gas: {gas}")
        my_logging.data('gas', gas)
        return tx_receipt
Example #17
0
    def dec(self, cipher: CipherValue, my_addr: AddressValue) -> Tuple[int, Optional[RandomnessValue]]:
        """
        Decrypt cipher encrypted for my_addr.

        :param cipher: encrypted value
        :param my_addr: cipher is encrypted for this address
        :return: if symmetric -> (plain, None), if asymmetric (plain, randomness which was used to encrypt plain)
        """
        assert isinstance(cipher, CipherValue), f"Tried to decrypt value of type {type(cipher).__name__}"
        assert isinstance(my_addr, AddressValue)
        zk_print(f'Decrypting value {cipher} for {my_addr}', verbosity_level=2)

        if cipher == CipherValue(params=self.params):
            # Ciphertext is all zeros, i.e. uninitialized -> zero
            return 0, (None if self.params.is_symmetric_cipher() else RandomnessValue(params=self.params))
        else:
            sk = self.keystore.sk(my_addr)
            plain, rnd = self._dec(cipher[:], sk.val)
            return plain, (None if rnd is None else RandomnessValue(rnd, params=self.params))
Example #18
0
    def getPk(self, address: AddressValue) -> PublicKeyValue:
        """
        Return public key for address.

        If the key is cached locally, returned the cached copy, otherwise request from pki contract.

        NOTE: At the moment, the name of this function must match the name in the pki contract.

        :param address: address to which the public key belongs
        :raise BlockChainError: if key request fails
        :return: the public key
        """
        assert isinstance(address, AddressValue)
        zk_print(f'Requesting public key for address {address.val}', verbosity_level=2)
        if address in self.local_pk_store:
            return self.local_pk_store[address]
        else:
            pk = self.conn.req_public_key(address, self.crypto_params)
            self.local_pk_store[address] = pk
            return pk
Example #19
0
def check_compilation(filename: str,
                      show_errors: bool = False,
                      display_code: str = None):
    """
    Run the given file through solc without output to check for compiler errors.

    :param filename: file to dry-compile
    :param show_errors: if true, errors and warnings are printed
    :param display_code: code to use when displaying the compiler errors
    :raise SolcException: raised if solc reports a compiler error
    """
    sol_name = pathlib.Path(filename).name
    with open(filename) as f:
        code = f.read()
    display_code = code if display_code is None else display_code

    had_error = False
    try:
        errors = compile_solidity_json(filename, None, -1, ())
        if not show_errors:
            return
    except SolcError as e:
        errors = json.loads(e.stdout_data)
        if not show_errors:
            raise SolcException()

    # if solc reported any errors or warnings, print them and throw exception
    if 'errors' in errors:
        zk_print()
        errors = sorted(errors['errors'], key=get_error_order_key)

        fatal_error_report = ''
        for error in errors:
            from zkay.utils.progress_printer import colored_print, TermColor
            is_error = error['severity'] == 'error'

            with colored_print(
                    TermColor.FAIL if is_error else TermColor.WARNING):
                if 'sourceLocation' in error:
                    file = error['sourceLocation']['file']
                    if file == sol_name:
                        line, column = _get_line_col(
                            code, error['sourceLocation']['start'])
                        report = f'{get_code_error_msg(line, column + 1, str(display_code).splitlines())}\n'
                        had_error |= is_error
                    else:
                        report = f"In imported file '{file}' idx: {error['sourceLocation']['start']}\n"
                report = f'\n{error["severity"].upper()}: {error["type"] if is_error else ""}\n{report}\n{error["message"]}\n'

                if is_error:
                    fatal_error_report += report
                elif 'errorCode' not in error or error['errorCode'] not in [
                        '1878'
                ]:  # Suppress SPDX license warning
                    zk_print(report)

        zk_print()
        if had_error:
            raise SolcException(fatal_error_report)
Example #20
0
    def _generate_or_load_key_pair(self, address: str) -> KeyPair:
        key_file = os.path.join(cfg.data_dir, 'keys', f'paillier_{self.params.key_bits}_{address}.bin')
        os.makedirs(os.path.dirname(key_file), exist_ok=True)
        if not os.path.exists(key_file):
            zk_print(f'Key pair not found, generating new Paillier secret...')
            pk, sk = self._generate_key_pair()
            self._write_key_pair(key_file, pk, sk)
            zk_print('Done')
        else:
            # Restore saved key pair
            zk_print(f'Paillier secret found, loading from file {key_file}')
            pk, sk = self._read_key_pair(key_file)

        return KeyPair(PublicKeyValue(pk, params=self.params), PrivateKeyValue(sk))
Example #21
0
    def _generate_or_load_key_pair(self, address: str) -> KeyPair:
        key_file = os.path.join(cfg.data_dir, 'keys', f'ec_{address}.bin')
        os.makedirs(os.path.dirname(key_file), exist_ok=True)
        if not os.path.exists(key_file):
            # Generate fresh randomness for ec private key
            zk_print(f'Key pair not found, generating new EC secret...')
            rnd = secrets.token_bytes(32)

            # Store randomness so that address will have the same key every time
            with open(key_file, 'wb') as f:
                f.write(rnd)
            zk_print('done')
        else:
            # Restore saved randomness
            zk_print(f'EC secret found, loading from file {key_file}')
            with open(key_file, 'rb') as f:
                rnd = f.read()

        # Derive keys from randomness
        pk, sk = self._gen_keypair(rnd)

        return KeyPair(PublicKeyValue([pk], params=self.params),
                       PrivateKeyValue(sk))
Example #22
0
    def transact(self, contract_handle, sender: AddressValue, function: str, actual_args: List, should_encrypt: List[bool], wei_amount: Optional[int] = None) -> Any:
        """
        Issue a transaction for the specified function in the given contract with the provided arguments

        **WARNING: THIS ISSUES A CRYPTO CURRENCY TRANSACTION (GAS COST)**

        :param contract_handle: the contract in which the function resides
        :param sender: sender address, its eth private key must be hosted in the eth node to which the backend connects.
        :param function: name of the function
        :param actual_args: the function argument values
        :param should_encrypt: a list which contains a boolean value for each argument, which should be true if the corresponding
                               parameter expects an encrypted/private value (this is only used for a last sanity-check)
        :param wei_amount: how much money to send along with the transaction (only for payable functions)
        :raise BlockChainError: if there is an error in the backend
        :raise TransactionFailedException: if the transaction failed
        :return: backend-specific transaction receipt
        """
        assert contract_handle is not None
        self.__check_args(actual_args, should_encrypt)
        zk_print(f'Issuing transaction for function "{function}" from account "{sender}"')
        zk_print(Value.collection_to_string(actual_args), verbosity_level=2)
        ret = self._transact(contract_handle, sender.val, function, *Value.unwrap_values(actual_args), wei_amount=wei_amount)
        zk_print()
        return ret
Example #23
0
def print_step(name):
    zk_print(f'{name}... ', end='', flush=True)
    yield
    zk_print('done')
Example #24
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
Example #25
0
 def _generate_keys_par(self, circuit: CircuitHelper):
     self._generate_keys(circuit)
     with finish_counter.get_lock():
         finish_counter.value += 1
         zk_print(f'Generated keys for circuit '
               f'\'{circuit.verifier_contract_type.code()}\' [{finish_counter.value}/{c_count}]')