Usage Guide
This guide provides a step-by-step walkthrough on how to use the ZK Email Verifier. It covers the process from converting your email to regex to creating your zero-knowledge proof for email verification. The guide assumes you have already installed the necessary packages as outlined in the Installation Guide. If you haven't done so, please refer to the Setup Guide first.
For an easy setup, we suggest utilizing zkrepl, a playground for compiling and testing your circuits in the early stages of development. Additionally, explore our Proof of Twitter guide for a practical demonstration on how to leverage our SDKs to construct your own circuits.
Step 1: Create the Regex File
Transform your target email string into a regex format and compile it into a regex.circom file. For guidance, visit our zk-regex repository.
If you want an easy way to use zk-regex V1, you can use our tool to convert regex into circom code: zkregex.com. If that fails, you are likely using advanced regex syntax and should use the zk-regex V2 CLI to compile it instead.
Step 2: Implementing the Circuits
Next, use email-verifier.circom
from the zk-email/circuits package to create your zk circuit to verify the DKIM signature. You should use our Twitter circuit guide as a reference.
To set up your own circuit for the email-verifier, you can follow these steps:
- Include the
email-verifier.circom
file from the@zk-email/circuits
package as well as theregex.circom
file that was generated from Step 1 of this guide. - Create a template for your circuit, let's call it
MyCircuit
. - Define the input signal for your circuit.
- Add any necessary witnesses and constraints for regex (learn more about how to setup regex here)
- Define the output signal, which will be public.
- Instantiate the EmailVerifier component within your
MyCircuit
template.
NOTE: For teams using the email verifier circuit with the ignoreBodyHashCheck
option disabled, please be aware of an important consideration. If you are conducting a body hash check within your own circuit configurations, it is essential to implement AssertZeroes
for all characters beyond the specified limit.
AssertZeroPadding(maxBodyLength)(emailBody, emailBodyLength + 1)
- maxBodyLength: maximum number of bytes that the email body can occupy.
- emailBody: email body content that has been padded to meet the required size for processing.
- emailBodyLength: length of the email body data including the padding.
Here's an example of how you can set up your own circuit:
include "@zk-email/circuits/email-verifier.circom";
include "simple_regex.circom"
template MyCircuit() {
signal input ...// inputs to your circuit
// Witnesses and constraints for regex go here
signal output ...// output that is public
component emailVerifier = EmailVerifier(_<arguments>_)(_<inputs>_);
signal (_<output1>_, _<output_array>_[_<array_length>_]) <== SimpleRegex(_<arguments>_)(_<inputs>_);
}
Step 3: Compile your circuit
To compile the circuit locally, you need to have Rust and Circom installed first. You can visit this link to install both https://docs.circom.io/getting-started/installation/#installing-dependencies
circom -l node_modules MyCircuit.circom -o --r1cs --wasm --sym --c --O0
Note: You can add -l to specify the directory where the directive include
should look for the circuits indicated. For our repo use circom -l node_modules
instead of circom. Additionally, we generally recommend using the --O0
flag for optimization during compilation for beginners. However, if you're more experienced with Circom, feel free to use the --O1
flag instead. It's important to avoid using the --O2
flag as that is the default setting and it may lead to the deletion of additional constraints.
After running this command, the circuit will be compiled into a .r1cs
file, a .wasm
file, and a .sym
file. These files are used in the next steps to generate the proving and verifying keys, and to compute the witness.
Step 4: Generate Circuit Inputs
The second step in the zkEmail verification process is to generate the necessary circuit inputs. This involves parsing the email headers to extract the required information.
The required information includes the RSA signature, public key, body of the email, and hash of the email body. To do this, create a directory named 'emls' and place the raw email inside it.
Next, create an inputs.ts
file. In this file, you will use the generateEmailVerifierInputs
function from the @zk-email/helpers
package.
Here is a sample code snippet from our proof-of-twitter example to guide you:
import { bytesToBigInt, fromHex } from "@zk-email/helpers/dist/binaryFormat";
import { generateEmailVerifierInputs } from "@zk-email/helpers/dist/input-generators";
export const STRING_PRESELECTOR = "email was meant for @";
export type IExampleCircuitInputs = {
twitterUsernameIndex: string;
address: string;
emailHeader: string[];
emailHeaderLength: string;
pubkey: string[];
signature: string[];
emailBody?: string[] | undefined;
emailBodyLength?: string | undefined;
precomputedSHA?: string[] | undefined;
bodyHashIndex?: string | undefined;
};
export async function generateExampleVerifierCircuitInputs(
email: string | Buffer,
ethereumAddress: string
): Promise<ITwitterCircuitInputs> {
const emailVerifierInputs = await generateEmailVerifierInputs(email, {
shaPrecomputeSelector: STRING_PRESELECTOR,
});
const bodyRemaining = emailVerifierInputs.emailBody!.map((c) => Number(c)); // Char array to Uint8Array
const selectorBuffer = Buffer.from(STRING_PRESELECTOR);
const usernameIndex =
Buffer.from(bodyRemaining).indexOf(selectorBuffer) + selectorBuffer.length;
const address = bytesToBigInt(fromHex(ethereumAddress)).toString();
return {
...emailVerifierInputs,
twitterUsernameIndex: usernameIndex.toString(),
address,
};
}
To generate the input.json
file, run the following command:
npx ts-node inputs.ts
Note: Increasing the emailHeaderLength
or emailBodyLength
sizes will increase the time required for compiling and creating proving keys.
Step 5: Compute the Witness
The process of creating a proof involves ensuring that all signals in the file adhere to the existing constraints. This is achieved by computing the witness using the Wasm file generated during compilation.
Navigate to the MyCircuit_js file and incorporate the input.json file that was produced in Step 2. Execute the following command in your terminal:
node generate_witness.js myCircuit.wasm input.json witness.wtns
This operation will produce a file named 'witness.wtns'. This file is encoded in a format that is compatible with the snarkjs library. In the following steps, we will utilize snarkJs to initiate the proof creation process.
Step 6: Proving the Circuit
The next step involves using the snarkjs
command-line tool to generate the keys and the verifier contract. If you haven't installed snarkjs
globally, you can do so by running npm install -g snarkjs
.
The generation of the zk proof requires a trusted setup, which includes the powers of tau ceremony
and phase 2
. Click here to read more about Trusted Setup.
Firstly, you need to determine the constraint size of your circuit. You can do this by running the following command:
snarkjs r1cs info myCircuit.r1cs
Memory Allocation for snarkjs
To avoid out-of-memory errors in snarkjs
for large circuits, increase Node.js memory with node --max-old-space-size=<size>
, where <size>
is in kilobytes.
node --max-old-space-size=614400 ./../node_modules/.bin/snarkjs
Powers of Tau
You can download the ptau file directly from Google Cloud Platform using the following command:
wget https://storage.googleapis.com/zkevm/ptau/powersOfTau28_hez_final_22.ptau
Phase 2
To run Phase 2 of the trusted setup refer to this link: https://github.com/iden3/snarkjs
Step 7: Verify your circuit
After this process you should get three files
- the proving key(vkey)
- proof.json
- public.json
You can use these files to verify your circuit.
Off-chain Verification
To verify your proof run:
snarkjs groth16 verify verification_key.json public.json proof.json
The command uses the files verification_key.json we exported earlier,proof.json and public.json to check if the proof is valid. If the proof is valid, the command outputs an OK.
On-chain Verification
To create a solidity verifier that allows you to verify your proof on the ethereum blockchain, run:
snarkjs zkey export solidityverifier myCircuit_0001.zkey verifier.sol
This will generate a verifier.sol
contract.