A proof of concept of a method to disperse funds to targeted recipients using zero knowledge proofs (generated with the ZKEmail library) and an oracle (Chainlink Functions). More specifically, it enables on-chain identity verification via a cryptographic proof from an email and then disperses the relevant amount of USDC based on a mapping of identities to amounts stored in a database accessed by the oracle.
This project creates a platform that connects “donors” to UCLA students who purchase items from a retailer, allowing them to redeem an equivalent amount of USDC. For example, students who buy books at a retailer can receive USDC equal to their book purchases.
- Donor deposits USDC in a smart contract.
- Student (Joe Bruin) makes a purchase with a known email address.
- Retailer records that purchase amount in their database.
- Student proves to a smart contract that they control that email via a zero-knowledge proof.
- Student claims the USDC equivalent of their purchase from the smart contract, with the help of a Chainlink Functions oracle that checks the database.
- Donor Deposits USDC
- A donor calls
depositUSDC()
onTheFund.sol
to deposit USDC.
- A donor calls
- Student Purchase
- Joe Bruin, with email [email protected], makes a $23.94 purchase at an online retailer.
- The retailer’s DB maps "[email protected]" to "$23.94".
- Proof Generation
- Joe Bruin generates a cryptographic proof with a zkey generated from the
circuits/src/DhruvEmailVerifier.circom
circuit. - Joe generates the proof using values from an email he received at "[email protected]" as the inputs to the circuit.
- The proof allows Joe Bruin to prove to others that he is a UCLA student in control of the email address [email protected].
- Joe Bruin generates a cryptographic proof with a zkey generated from the
- On-Chain Verification
- Joe Bruin submits this proof to the
VerifyEmail()
function ofcontracts/src/TheFund.sol
using his ethereum address 0xabc. - When the smart contract verifies the proof, "[email protected]" is stored in a mapping in the smart contract as: "[email protected]"-->0xabc.
- Joe Bruin submits this proof to the
- Redeem USDC
- Joe Bruin calls the
claimFunds()
function fromcontracts/src/TheFund.sol
to redeem some amount of USDC corresponding to his purchase of books from the book store. claimFunds()
callsrequestPriceData()
from the EmailOracle smart contract.requestPriceData()
uses Chainlink Functions service to make a request to the API "https://y3ugr0hill.execute-api.us-east-1.amazonaws.com/production/[email protected]" (a mockAPI I created to mimick a book store's database).- This API request and corresponding oracle request will return $23.94, because that is the value in the database that is saved corresponding to "[email protected]".
- Upon the oracle request returning, the function
fulfill()
from the EmailOracle contract will calltransferUSDC()
from the TheFund contract. transferUSDC()
will send $23.94 to Joe Bruin's ethereum address.
- Joe Bruin calls the
- The oracle has access to the book retailer’s purchase database via an API.
- Donors are willing to donate USDC into the smart contract.
The Circom circuits require the verifying email to be sent by [email protected] and to include an address matching [a-zA-Z0-9._]+@g\.ucla\.edu
in the body. Thus, Joe Bruin must receive an email from [email protected] stating "[email protected]" in the message body to produce a valid proof.
Examples of valid EML files can be found in circuits/helpers/emls
.
The circuits/src
folder contains the circom circuits used to generate proofs that verify someone is a UCLA student. Essentially, someone will submit an eml file (which is simply an email that they downloaded) to generate a proof based on these circuits. To understand how ZKEmail's library of circuits works to create cryptographic proofs of the contents and metadata of emails, read more here: https://docs.zk.email/architecture/dkim-verification.
- circuits/src/DhruvEmailVerifier.circom
Main circuit that checks DKIM signatures and extracts email metadata. - circuits/src/Dhruv_Sender_Regex.circom
Regex-based sub-circuit (generated by zkregex.com) to verify the sender is [email protected]. - circuits/src/UCLA_Email_Regex.circom
Regex-based sub-circuit (generated by zkregex.com) to verify the presence of a UCLA email in the body. Email address needs to match the regex[a-zA-Z0-9._]+@g\.ucla\.edu
. - circuits/helpers/inputs.ts
Generates the input JSON for the circuits from an EML file. You can find example EML files in circuits/helpers/emls that can successfully produce a valid proof.
The contracts/src folder contains the Solidity smart contracts that implement the on-chain logic for verifying proofs and managing USDC flows (deposits, claims, and disbursements).
-
TheFund.sol
- Main entry point for users who want to verify their email proofs and claim funds.
- Key Functions:
- VerifyEmail():
- Accepts the zero-knowledge proof parameters.
- Calls the verifier.sol’s
verifyProof()
function to validate the proof. - If valid, maps the user’s email address to their Ethereum address.
- claimFunds(emailAddress):
- Initiates the process of retrieving the user’s purchase amount from the book retailer database (via EmailOracle.sol).
- Once the oracle returns the purchase amount, triggers the USDC disbursement to the user’s Ethereum address.
- depositUSDC(amount):
- Allows donors to deposit or “donate” USDC into the contract so that funds are available for students to claim.
- VerifyEmail():
-
EmailOracle.sol
- Interacts with the Chainlink Functions service to fetch purchase amounts from the external “book purchases” database.
- Key Functions:
- requestPriceData(emailAddress):
- Requests the total cost of purchases corresponding to emailAddress saved in the bookstore database.
- fullfil(_requestId, _price): - Is the callback function for the oracle service, where _price is the returned price amount. - This function then calls transferUSDC(emailAddr, _amount) with the amount spent by emailAddr.
- requestPriceData(emailAddress):
-
verifier.sol
- Purpose: Provides the low-level proof verification logic automatically generated by Circom and snarkjs.
- Key Functions:
- verifyProof(): Validates the zero-knowledge proof data passed from TheFund.sol. If the proof is valid, TheFund.sol proceeds to map the email to the user’s address.
Follow these steps in the circuits/src folder, these steps are analagous to the Usage Guide for ZKEmail: https://docs.zk.email/zk-email-verifier/usage-guide
- Run
yarn install
- Follow the ZKEmail setup guide to download necessary packages: https://docs.zk.email/zk-email-verifier/setup
- Install Rust and Circom: https://docs.circom.io/getting-started/installation/#installing-dependencies
- Compile the DhruvEmailVerifier.circom circuit
circom -l ../node_modules DhruvEmailVerifier.circom -o --r1cs --wasm --sym --c --O0
- Enter circuits/helpers and run
npx ts-node inputs.ts
to generate the inputs for the circuit. You can see at the bottom of inputs.ts that it is preset to use the LakEmail.eml file to generate inputs. - Compute the witness with the command
node DhruvEmailVerifier_js/generate_witness.js DhruvEmailVerifier_js/DhruvEmailVerifier.wasm ../helpers/inputLak.json witness.wtns
- Run Phase 2 of the Powers of Tau Ceremony. Follow the initial setup steps and then start from step 11 here: https://github.com/iden3/snarkjs
- To avoid out-of-memory errors in snarkjs for large circuits, increase Node.js memory with node --max-old-space-size=, where is in kilobytes.
node --max-old-space-size=614400 ./../node_modules/.bin/snarkjs
- To download the powersoftau file:
wget https://storage.googleapis.com/zkevm/ptau/powersOfTau28_hez_final_22.ptau
- This is computationally intensive, so a lot of these steps I've needed run on an AWS EC2 isntance.
- To avoid out-of-memory errors in snarkjs for large circuits, increase Node.js memory with node --max-old-space-size=, where is in kilobytes.
- Once you have your proof, run
snarkjs zkey export soliditycalldata public.json proof.json
to get the inputs for the verify() function from the smart contract.
- Deposit USDC into TheFund.sol.
- Call
VerifyEmail()
in TheFund.sol with the paramters being the result of step 8 from part 1. - Then call
claimFunds()
with the email address parameter being the receiver of the email used to generate the proof in part 1. For the example given from earlier in the ReadMe file, the parameter would be "[email protected]". - Wait about 15-30 seconds for the oracle to complete its process and USDC to be sent to your wallet.
ZKEmail creates a very powerful ability to link Web2 and Web3 identity, allowing people to prove some sort of web2 identity (in this case email address) on-chain. I built on that to programmatically send/receive money based on a web2 identity.
For example, this project could be altered to fund teachers looking to purchase school supplies from Staples (or any other retailer of that type) to stock their classrooms. First, a teacher would purchase school supplies from Staples, then Staples would save her purchase amount alongside her school district email address in their database (ex. "[email protected]"-->$88.61). Then, the teacher would generate a proof using her school supplied email address to prove she is a teacher at Miller Creek Middle School. Finally, a smart contract with API access via an oracle to a Staples database could verify the amount of the teachers' purchase to distribute back to them.
As another example, the UCLA Internet Research Initiative could use a project like this. The UCLA Internet Research Initiative gives 10-15 students $7500 over the course of a school year to spend towards a research project. Every student who wins the 'research scholarship' receives a congradulatory email from Professor Leonard Kleinrock. A project with a framework similar to this one could be created where students who win the research scholarship use their congradulatory email to generate a cryptographic proof, then submit the proof to a smart contract which verifies the validity of the email and disperses $7500 to every person who submits a valid proof. This example would not require an Oracle.
- Create a frontend for proof generation.
- Create some form of limit on withdraws. You don't want a student to buy $1000 worth of books, then claim $1000 from the smart contract, and then resell all of the books online to make a proft. The amount that can be withdrawan should be limited below $100.
- Prevent someone from claiming money twice from the same purchase (very simple lol just forgot to do this earlier).
- Get access to some online retailer's database of purchases.
- Create some type of work around for the Chainlink Functions because Functions is in Beta.