Introduction to zero knowledge proof contract and application development of Ethereum

In this tutorial, we will learn how to develop a zero knowledge identification DApp based on Ethereum, how to develop a zero knowledge circuit of circle, how to generate and verify a solid zero knowledge smart contract, and how to generate zero knowledge evidence under the chain using Javascript, and provide a complete source code download at the end of the tutorial.

Blockchain development tutorial link: Ethereum |Bitcoin | EOS | Tendermint | Hyperledger Fabric | Omni/USDT | Ripple

1. Overview of zero knowledge proof of identity DApp

We will develop a zero knowledge application to prove that a user belongs to a specific group without disclosing the specific information of the user. The use process is as follows:

Our development process is divided into the following steps:

  • Develop zero knowledge circuit
  • Generate a solid library for verifying zero knowledge circuit
  • Develop smart contracts and integrate the above solid Libraries
  • Generate evidence locally and verify on Chain

2. Construction of DApp development environment of zero knowledge proof Ethereum

Just as you can develop web applications without fully understanding the HTTP protocol, there are many tools that can help develop knowledge-based dapps without the need for cryptography or mathematics.

I recommend the following development languages and tool chains:

  • JavaScript / typescript: the application is developed with JavaScript / typescript, because they are well supported in the Ethereum ecosystem
  • Solidity: smart contracts are developed with solidity because it's mature and the community is good
  • Truffle: using truffle as a framework for smart contract development and deployment
  • Circle: using circle to develop zero knowledge proof circuit

3. Development of zero knowledge circuit of CIRCOM: judge whether the private key matches the public key set

Our goal is to create a circuit that can determine whether the input private key corresponds to one of the input public key sets. The pseudo code of the circuit is as follows:

// Note that a private key is a scalar value (int)
// whereas a public key is a point in space (Tuple[int, int])
const zk_identity = (private_key, public_keys) => {
  // derive_public_from_private is a function that
  // returns a public key given a private key
  derived_public_key = derive_public_from_private(private_key)
  for (let pk in public_keys):
    if derived_public_key === pk:
      return true
  return false
}

Now we are going to start to write zero knowledge circuit with circom. The syntax of circom can be found in Official documents.

First create the project folder and install the necessary dependent packages:

npm install circom circomlib snarkjs websnark

mkdir contracts
mkdir circuits
mkdir -p build/circuits

touch circuits/circuit.circom

Now write the circuit file circuit.circom First, introduce the necessary basic circuits and define the PublicKey template:

include "../node_modules/circomlib/circuits/bitify.circom";
include "../node_modules/circomlib/circuits/escalarmulfix.circom";
include "../node_modules/circomlib/circuits/comparators.circom";

template PublicKey() {
  // Note: private key needs to be hashed, and then pruned
  // to make sure its compatible with the babyJubJub curve
  signal private input in;
  signal output out[2];

  component privBits = Num2Bits(253);
  privBits.in <== in;

  var BASE8 = [
    5299619240641551281634865583518297030282874472190772894086521144482721001553,
    16950150798460657717958625567821834550301663161624707787222815936182638968203
  ];

  component mulFix = EscalarMulFix(253, BASE8);
  for (var i = 0; i < 253; i++) {
    mulFix.e[i] <== privBits.out[i];
  }

  out[0] <== mulFix.out[0];
  out[1] <== mulFix.out[1];
}

The function of PublicKey template is to find out the corresponding public key (circuit output) of private key (circuit input) on babyJubJub curve. Note that in the above circuit, we declare the input private key as a private signal, so the generated evidence will not contain any information that can reconstruct the input private key.

Once the above basic modules are completed, we can now build the main logic of our zero knowledge proof circuit to verify whether the specified users belong to a group:

include ...

template PublicKey() {
  ...
}

template ZkIdentity(groupSize) {
  // Public Keys in the smart contract
  // Note: this assumes that the publicKeys
  // are all unique
  signal input publicKeys[groupSize][2];

  // Prover's private key
  signal private input privateKey;

  // Prover's derived public key
  component publicKey = PublicKey();
  publicKey.in <== privateKey;

  // Make sure that derived public key needs to
  // matche to at least one public key in the
  // smart contract to validate their identity
  var sum = 0;

  // Create a component to check if two values are
  // equal
  component equals[groupSize][2];
  for (var i = 0; i < groupSize; i++) {
    // Helper component to check if two
    // values are equal
    // We don't want to use ===
    // as that will fail immediately if
    // the predicate doesn't hold true
    equals[i][0] = IsEqual();
    equals[i][1] = IsEqual();

    equals[i][0].in[0] <== publicKeys[i][0];
    equals[i][0].in[1] <== publicKey.out[0];

    equals[i][1].in[0] <== publicKeys[i][1];
    equals[i][1].in[1] <== publicKey.out[1];

    sum += equals[i][0].out;
    sum += equals[i][1].out;
  }

  // equals[i][j].out will return 1 if the values are equal
  // and 0 if the values are not equal
  // Therefore, if the derived public key (a point in space)
  // matches a public keys listed in the smart contract, the sum of
  // all the equals[i][j].out should be equal to 2
  sum === 2;
}


// Main entry point
component main = ZkIdentity(2);

Now we compile, set, and generate the circuit's solid validator:

$(npm bin)/circom circuits/circuit.circom -o build/circuits/circuit.json

# snarkjs setup might take a few seconds
$(npm bin)/snarkjs setup --protocol groth -c build/circuits/circuit.json --pk build/circuits/provingKey.json --vk build/circuits/verifyingKey.json

# Generate solidity lib to verify proof
$(npm bin)/snarkjs generateverifier --pk build/circuits/provingKey.json --vk build/circuits/verifyingKey.json -v contracts/Verifier.sol

# You should now have a new "Verifier.sol" in your contracts directory
# $ ls contracts
# Migrations.sol Verifier.sol

Note that we use the growth protocol to generate proof key and verification key, because we want to use websnark to generate evidence, because websnark has much better performance than snarkjs.

Once the above steps are completed, we have realized zero knowledge proof logic. In the following sections, we will show you how to use the generated solid zero knowledge validation contract.

4. Solid zero knowledge verification contract

After setting the zero knowledge circuit, a new circuit named Verifier.sol The solidity library for. If you look at the contents of this file, you will see that it contains the following functions:

...

  function verifyProof(
            uint[2] memory a,
            uint[2][2] memory b,
            uint[2] memory c,
            uint[4] memory input
        ) public view returns (bool r) {
        Proof memory proof;
        proof.A = Pairing.G1Point(a[0], a[1]);
        proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
        proof.C = Pairing.G1Point(c[0], c[1]);
        uint[] memory inputValues = new uint[](input.length);
        for(uint i = 0; i < input.length; i++){
            inputValues[i] = input[i];
        }
        if (verify(inputValues, proof) == 0) {
            return true;
        } else {
            return false;
        }
    }

...

This is an auxiliary function to verify the validity of zero knowledge evidence. The verifyProof function takes four parameters, but we only care about the input parameter which represents the common input of the circuit. We will use it to verify the identity of the user in the smart contract code. Let's take a look at the specific implementation code:

pragma solidity 0.5.11;

import "./Verifier.sol";

contract ZkIdentity is Verifier {
    address public owner;
    uint256[2][2] public publicKeys;

    constructor() public {
        owner = msg.sender;
        publicKeys = [
            [
                11588997684490517626294634429607198421449322964619894214090255452938985192043,
                15263799208273363060537485776371352256460743310329028590780329826273136298011
            ],
            [
                3554016859368109379302439886604355056694273932204896584100714954675075151666,
                17802713187051641282792755605644920157679664448965917618898436110214540390950
            ]
        ];
    }

    function isInGroup(
        uint256[2] memory a,
        uint256[2][2] memory b,
        uint256[2] memory c,
        uint256[4] memory input // public inputs
    ) public view returns (bool) {
        if (
            input[0] != publicKeys[0][0] &&
            input[1] != publicKeys[0][1] &&
            input[2] != publicKeys[1][0] &&
            input[3] != publicKeys[1][1]
        ) {
            revert("Supplied public keys do not match contracts");
        }

        return verifyProof(a, b, c, input);
    }
}

We create a new contract ZkIdentity.sol , which inherits from the generated Verifier.sol There is an initial group with two member public keys and a function named isInGroup. The function first verifies that the open input signal of the circuit is consistent with the group in the smart contract, and then returns the verification result of the input evidence.

The logic is not complicated, but it does meet our goal: to verify that a user belongs to a specific group without revealing who the user is.

You need to deploy the contract to the chain before proceeding to the next section.

5. Generating zero knowledge evidence with JavaScript and interacting with smart contract

Once we have completed the zero knowledge circuit and implemented the smart contract logic, we can generate evidence and call the isInGroup method of the smart contract to verify.

The following pseudo code shows how to generate evidence and validate it with a smart contract, which you can access here Check the complete js code:

// Assuming below already exists
const provingKey // provingKey.json
const circuit // zero-knowledge circuit we wrote
const zkIdentityContract // Zk-Identity contract instance

const privateKey  // Private key that corresponds to one of the public key in the smart contract
const publicKeys = [
  [
      11588997684490517626294634429607198421449322964619894214090255452938985192043n,
      15263799208273363060537485776371352256460743310329028590780329826273136298011n
  ],
  [
      3554016859368109379302439886604355056694273932204896584100714954675075151666n,
      17802713187051641282792755605644920157679664448965917618898436110214540390950n
  ]
]

const circuitInputs = {
  privateKey,
  publicKeys
}

const witness = circuit.calculateWitness(circuitInputs)
const proof = groth16GenProof(witness, provingKey)

const isInGroup = zkIdentityContract.isInGroup(
  proof.a,
  proof.b,
  proof.c,
  witness.publicSignals
)

Running js code can prove that you belong to a group without revealing who you are!

Complete code for the tutorial Download address.

Original link: Zero knowledge proof DApp development practice - huizhi.com

Tags: Blockchain JSON Javascript npm TypeScript

Posted on Fri, 29 May 2020 06:04:06 -0400 by Toonster