Skip to content

Commit

Permalink
WIP: Add remote proof generation using zk-regex-sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
wryonik committed Jul 22, 2024
1 parent 0b149b6 commit a54e05b
Show file tree
Hide file tree
Showing 17 changed files with 2,100 additions and 1,344 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@
"keywords": [],
"author": "",
"license": "MIT",
"packageManager": "[email protected]"
"packageManager": "[email protected]",
"resolutions": {
"zk-regex-sdk": "portal:/Users/savitar/Projects/opensource/proof-of-twitter/packages/app/zk-regex-sdk"
}
}
2 changes: 1 addition & 1 deletion packages/app/.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
VITE_CONTRACT_ADDRESS=0x21d42CC2EcDb6db2c3D9b494D8CCcb6674360912
VITE_CIRCUIT_ARTIFACTS_URL=https://storage.googleapis.com/proof-of-twitter-artifacts/b6ea02e/
VITE_GOOGLE_CLIENT_ID=274960270901-ciaimkfeslrae1955ghq8bv7n8n3n71f.apps.googleusercontent.com
# VITE_GOOGLE_CLIENT_ID=274960270901-ciaimkfeslrae1955ghq8bv7n8n3n71f.apps.googleusercontent.com
13 changes: 8 additions & 5 deletions packages/app/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import "@rainbow-me/rainbowkit/styles.css";
import { GoogleOAuthProvider } from "@react-oauth/google";
import { GoogleAuthProvider } from "./contexts/GoogleAuth";
import { ZkRegexProvider } from "../zk-regex-sdk";

const { connectors } = getDefaultWallets({
appName: "ZK Email - Twitter Verifier",
Expand Down Expand Up @@ -48,11 +49,13 @@ if (import.meta.env.VITE_GOOGLE_CLIENT_ID) {
} else {
ReactDOM.render(
<React.StrictMode>
<WagmiConfig config={config}>
<RainbowKitProvider chains={[sepolia]} theme={darkTheme()}>
<App />
</RainbowKitProvider>
</WagmiConfig>
<ZkRegexProvider clientId={"274960270901-ciaimkfeslrae1955ghq8bv7n8n3n71f.apps.googleusercontent.com"} zkRegexRegistryUrl="https://registry-dev.zkregex.com">
<WagmiConfig config={config}>
<RainbowKitProvider chains={[sepolia]} theme={darkTheme()}>
<App />
</RainbowKitProvider>
</WagmiConfig>
</ZkRegexProvider>
</React.StrictMode>,
document.getElementById("root")
);
Expand Down
47 changes: 46 additions & 1 deletion packages/app/src/pages/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@ import {
} from "../hooks/useGmailClient";
import { formatDateTime } from "../helpers/dateTimeFormat";
import EmailInputMethod from "../components/EmailInputMethod";
import { useZkRegex } from "../../zk-regex-sdk";
import { randomUUID } from "crypto";

const CIRCUIT_NAME = "twitter";

export const MainPage: React.FC<{}> = (props) => {
const { address } = useAccount();
const workers = new Map<string, boolean>();

const {
googleAuthToken,
Expand All @@ -46,6 +49,14 @@ export const MainPage: React.FC<{}> = (props) => {
googleLogOut,
} = useGoogleAuth();

const {
createInputWorker,
generateInputFromEmail,
generateProofRemotely,
proofStatus,
inputWorkers,
} = useZkRegex();

const [ethereumAddress, setEthereumAddress] = useState<string>(address ?? "");
const [emailFull, setEmailFull] = useState<string>(
localStorage.emailFull || ""
Expand All @@ -61,6 +72,7 @@ export const MainPage: React.FC<{}> = (props) => {
const [lastAction, setLastAction] = useState<"" | "sign" | "verify" | "send">(
""
);
const [workerReady, setWorkerReady] = useState<boolean>(false);
const [isFetchEmailLoading, setIsFetchEmailLoading] = useState(false);
const [fetchedEmails, setFetchedEmails] = useState<RawEmailResponse[]>([]);
const [showBrowserWarning, setShowBrowserWarning] = useState<boolean>(false);
Expand Down Expand Up @@ -248,6 +260,32 @@ export const MainPage: React.FC<{}> = (props) => {
downloadZKey();
}, []);

const handleGenerateProofRemotely = async () => {
const input = await generateInputFromEmail(
"zk-email/proof-of-twitter-v2",
emailFull
);
const body = Buffer.from(input.emailBody).toString("utf-8");
console.log("input", input);
console.log(input);
const proofRes = await generateProofRemotely(
"zk-email/proof-of-twitter-v2",
input
);
console.log(proofRes);
};

useEffect(() => {
if (workers.get("zk-email/proof-of-twitter-v2")) {
return;
}
createInputWorker("zk-email/proof-of-twitter-v2");
workers.set("zk-email/proof-of-twitter-v2", true);
// setWorkerReady(true);
}, []);

console.log(inputWorkers);

return (
<Container>
{showBrowserWarning && (
Expand Down Expand Up @@ -393,7 +431,8 @@ export const MainPage: React.FC<{}> = (props) => {
)}
</div>
) : null}
{inputMethod === "EML_FILE" || !import.meta.env.VITE_GOOGLE_CLIENT_ID ? (
{inputMethod === "EML_FILE" ||
!import.meta.env.VITE_GOOGLE_CLIENT_ID ? (
<>
{" "}
<DragAndDropTextBox onFileDrop={onFileDrop} />
Expand Down Expand Up @@ -501,6 +540,12 @@ export const MainPage: React.FC<{}> = (props) => {
>
{displayMessage}
</Button>
<Button
data-testid="remote-prove-button"
onClick={handleGenerateProofRemotely}
>
Generate Proof Remotely
</Button>
{displayMessage ===
"Downloading compressed proving files... (this may take a few minutes)" && (
<ProgressBar
Expand Down
2 changes: 2 additions & 0 deletions packages/app/zk-regex-sdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/node_modules
/dist
Binary file added packages/app/zk-regex-sdk/.yarn/install-state.gz
Binary file not shown.
110 changes: 110 additions & 0 deletions packages/app/zk-regex-sdk/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
'use client';
import GoogleAuthProvider from "./src/providers/GoogleAuthProvider";
import GoogleAuthContext from "./src/contexts/GoogleAuth";
import ZkRegexContext, { ProofStatus } from "./src/contexts/ZkRegex";
import useGoogleAuth from "./src/hooks/useGoogleAuth";
import { fetchEmailList, fetchEmailsRaw, fetchProfile } from "./src/hooks/useGmailClient";
import { ReactNode, useState } from "react";
import { GoogleOAuthProvider } from '@react-oauth/google';
// import React from "react";
import useZkRegex from './src/hooks/useZkRegex';
import { encode } from 'js-base64';

interface ProvidersProps {
children: ReactNode;
clientId: string;
zkRegexRegistryUrl: string;
}

function ZkRegexProvider({children, clientId, zkRegexRegistryUrl}: ProvidersProps) {

const [inputWorkers, setInputWorkers] = useState<Record<string, Worker>>({});
const [proofStatus, setProofStatus] = useState<Record<string, ProofStatus>>({});

function createInputWorker(name: string): void {
fetch(`${zkRegexRegistryUrl}/api/script/circuit_input/${name}`, {headers: {
'Accept': 'text/javascript'
}}).then(async r => {
const js = await r.text();
const w = new Worker(`data:text/javascript;base64,${encode(js)}`)
setInputWorkers({...inputWorkers, [name]: w});
})
}

async function generateInputFromEmail(name: string, email: string) {
const worker = inputWorkers[name];
return new Promise((resolve, reject) => {
worker.onmessage = (event: any) => {
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
}
worker.postMessage(email);
});
}

async function generateProofRemotely(name: string, input: any) {
const res = await fetch(`${zkRegexRegistryUrl}/api/proof/${name}`, {
method: 'POST',
body: JSON.stringify(input),
headers: {
'Content-Type': 'application/json'
}
});
const data = await res.json();
setProofStatus((prev) => ({...prev, [data.id]:data}));
if (data.pollUrl) {
poolForProofStatus(data.pollUrl)
}
return data;
}

async function poolForProofStatus(url: string) {
const res = await fetch(url);
const data = await res.json();
setProofStatus((prev) => ({...prev, [data.id]:data}));
if (data.status !== 'COMPLETED') {
setTimeout(() => poolForProofStatus(url), 5000);
}
}

const contextValues = {
zkRegexRegistryUrl,
customInputGenWorkerSrc: {},
inputWorkers,
createInputWorker,
deleteInputWorker: function (name: string): void {
inputWorkers[name].terminate();
delete inputWorkers[name];
},
generateInputFromEmail,
customProofGenWorkerSrc: {},
proofWorkers: {},
createProofWorker: function (_name: string): void {
throw new Error("Function not implemented.");
},
deleteProofWorker: function (_name: string): void {
throw new Error("Function not implemented.");
},
generateProofLocally: async function (_name: string, _input: any): Promise<any> {
throw new Error("Function not implemented.");
},
proofStatus,
generateProofRemotely,
}


return (
<ZkRegexContext.Provider value={contextValues}>
<GoogleOAuthProvider clientId={clientId}>
<GoogleAuthProvider>
{children}
</GoogleAuthProvider>
</GoogleOAuthProvider>
</ZkRegexContext.Provider>
);
}

export { ZkRegexProvider, GoogleAuthContext, useGoogleAuth, fetchEmailList, fetchEmailsRaw, fetchProfile, useZkRegex }
22 changes: 22 additions & 0 deletions packages/app/zk-regex-sdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "zk-regex-sdk",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"scripts": {
"build": "tsc"
},
"devDependencies": {
"@types/react": "^18.3.3",
"react": "^18.3.1",
"typescript": "^5"
},
"dependencies": {
"@react-oauth/google": "^0.12.1",
"js-base64": "^3.7.7"
},
"peerDependencies": {
"react": "^18.3.1"
}
}
23 changes: 23 additions & 0 deletions packages/app/zk-regex-sdk/src/contexts/GoogleAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createContext } from 'react';

interface GoogleAuthValues {
googleAuthToken: any | null;
isGoogleAuthed: boolean;
loggedInGmail: string | null;
scopesApproved: boolean;
googleLogIn: () => void;
googleLogOut: () => void;
}

const defaultValues: GoogleAuthValues = {
googleAuthToken: null,
isGoogleAuthed: false,
loggedInGmail: null,
scopesApproved: false,
googleLogIn: () => { console.log("context not setup")},
googleLogOut: () => {},
};

const GoogleAuthContext = createContext<GoogleAuthValues>(defaultValues)

export default GoogleAuthContext
65 changes: 65 additions & 0 deletions packages/app/zk-regex-sdk/src/contexts/ZkRegex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { createContext } from 'react';

export interface ProofStatus {
status: string,
id: string,
pollUrl: string,
estimatedTimeLeft: number
publicOutput: any,
proof: any,
}

interface ZkRegexValues {
zkRegexRegistryUrl: string

// The worker source code for generating the circuit inputs
// If not provided, the default worker scripts from zk regex registry will be used
// Format: { "zk-email/twitter-proof": "https://example.com/generate_inputs_worker_bundled.js" }
customInputGenWorkerSrc: {
[key: string]: string
}
inputWorkers: {
[key: string]: Worker
}
createInputWorker: (name: string) => void;
deleteInputWorker: (name: string) => void;
generateInputFromEmail: (name: string, email: string) => Promise<any>;

customProofGenWorkerSrc: {
[key: string]: string
}
proofWorkers: {
[key: string]: Worker
}
createProofWorker: (name: string) => void;
deleteProofWorker: (name: string) => void;
generateProofLocally: (name: string, input: any) => Promise<any>;

proofStatus: {
[key: string]: ProofStatus
}
generateProofRemotely: (name: string, input: any) => Promise<any>;
}

const defaultValues: ZkRegexValues = {
zkRegexRegistryUrl: "",

customInputGenWorkerSrc: {},
inputWorkers: {},
createInputWorker: () => { console.log("context not setup")},
deleteInputWorker: () => { console.log("context not setup")},
generateInputFromEmail: async () => { console.log("context not setup");},

customProofGenWorkerSrc: {},
proofWorkers: {},
createProofWorker: () => { console.log("context not setup")},
deleteProofWorker: () => { console.log("context not setup")},
generateProofLocally: async () => { console.log("context not setup");},

proofStatus: {},
generateProofRemotely: async () => { console.log("context not setup");}
}

const ZkRegexContext = createContext<ZkRegexValues>(defaultValues)

export default ZkRegexContext
Loading

0 comments on commit a54e05b

Please sign in to comment.