Hackathon: AI on the blockchain, zk-proof style

This repo shows how authorization on the blockchain can be done with a neural network. In short:

  1. We generate a random number with vrf function.
  2. We make a picture of our (secret) handwritten digit corresponding to the random number.
  3. We locally generate a proof with the digit as private input and the random number as public input. If the proof is valid we know a secret picture corresponding to the random number.
  4. We use the proof and the number as input to withdraw all ETH from the contract.

We show how to generate a zk-proof made with ZoKrates of a neural network made in PyTorch.

This has clear benefits as the validator run in logarithmic time with respect to the input circuit, enabling the memory and computationally heavy calculations of a neural network to be offloaded from the blockchain network itself. Saving a LOT of gas and storing costs on the blockchain.

Furthermore, the input can be a private field enabling authentication applications to use private input data without the risk of exposing user sensitive data, a prime example is facial recognition.

In our example we have an image of a handwritten digit that needs to correspond to a random number on chain generated by the chainlink vrf function.

We do this without exposing the image itself by providing the image as a private field.

Private fields are not included in the proof generated by ZoKrates (see zokrates/proof.json).

As ZoKrates only support unsigned fields, and max 64 bit unsigned integer we had to conjure a fixed precision field combined with a boolean for the sign (see zokrates/signed_field.zok).

As common Neural Network layers only consist of addition and mulitplication this was within the limitations of a fixed precision field with a boolean for a sign (see zokrates/model_params.zok).

We implemented fully connected layers and rectified-linear unit layers (ReLU) as a Proof of Concept (PoC) (see zokrates/layers.zok).

Finally, we resized the MNIST images to 14 x 14 to speed up the script (see zokrates/run.sh for input).

Setup

# install zokrates
curl -LSfs get.zokrat.es | sh


# in python/ directory
python3 -m venv venv
source venv/bin/activate
pip3 install -r requirements.txt

Generate model parameters

# in python/ directory

# create pytorch model
python3 neural_network.py

# create ZoKrates parameters
python3 generate_zok_params.py

# create run script
python3 generate_run_script.py

Compile, run, output Solidity contract and verify

# in zokrates/ directory
chmod +x ./run.sh
./run.sh

# to run all zokrates unit tests
./test_run.sh

Compile smart contract and run contract withdrawal script

# in hardhat directory

# copy verifier.sol in zokrates/ to Verifier.sol in hardhat/contracts/
cp ../zokrates/verifier.sol ./contracts/Verifier.sol

yarn install
yarn hardhat compile
yarn hardhat run scripts/withdraw.ts

Result run with seeded PyTorch

Pytorch has test accuracy of 91.6%. Running ./run.sh outputs...

$ ./run.sh

Compiling network.zok

Compiled code written to 'out'
Number of constraints: 3056050
Performing setup...
Verification key written to 'verification.key'
Proving key written to 'proving.key'
Setup completed
Computing witness...
"0x000000001ce96db0" true
"0x000000001c910917" false
"0x00000000014243bb" false
"0x00000000071f9205" true
"0x0000000011254f3c" true
"0x000000000ccde09c" true
"0x00000000092b9312" true
"0x000000000527195f" true
"0x0000000002bbc272" true
"0x0000000012288b84" true
max_index is "0x00000001"
Witness file written to 'witness'
Generating proof...
Proof written to 'proof.json'
Exporting verifier...
Verifier exported to 'verifier.sol'
Performing verification...
PASSED


$ yarn hardhat run scripts/withdraw.ts
...
got ZkNeuralNetwork contract at 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9
deposited 0.1 ETH into contract
Need to verify number 1 by making a proof that passes the zkNN
Withdrawed 0.1 ETH
New contract balance 0.0 ETH

Smart Contract

Solidity function for withdrawing all money in the contract, protected by a zkProof in the Verifier.sol contract (see hardhat/contracts/).

    function withdrawWithProofOfZkNeuralNetwork(
        uint a1,
        uint a2,
        uint b11,
        uint b12,
        uint b21,
        uint b22,
        uint c1,
        uint c2,
        uint[1] memory input
    ) external {
        // verify that the public data part of the proof equals the number given by the vrf. aka we're checking the right number.
        require(input[0] == s_numberToVerify, "Providing proof for wrong number");

        Verifier.Proof memory proof = Verifier.Proof(
            Pairing.G1Point(a1, a2),
            Pairing.G2Point([b11, b12], [b21, b22]),
            Pairing.G1Point(c1, c2)
        );

        bool verified = i_zkNeuralNetworkVerifier.verifyTx(proof, input);
        require(verified, "The neural network did not classifiy the input as correct!");

        uint256 balance = address(this).balance;
        (bool sent, ) = msg.sender.call{value: balance}("");
        require(sent, "Failed to send Ether");

        s_withdrawalState = WithdrawalState.Idle;
    }