Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}
};

module.exports = nextConfig
module.exports = nextConfig;
19 changes: 11 additions & 8 deletions src/lib/executeTransaction.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { Addresses } from '@/shared/addresses';
import { TransactionReceipt } from '@ethersproject/abstract-provider';
import { prepareWriteContract, writeContract } from '@wagmi/core';
import { Addresses } from "@/shared/addresses";
import { TransactionReceipt } from "@ethersproject/abstract-provider";
import { prepareWriteContract, writeContract } from "@wagmi/core";

export const executeTransaction = async (proof: any, publicSignals: Array<string>): Promise<TransactionReceipt> => {
const abiPath = require('./abi/SimpleMultiplier.json');
export const executeTransaction = async (
proof: any,
publicSignals: Array<string>
): Promise<TransactionReceipt> => {
const abiPath = require("./abi/SimpleMultiplier.json");

// Prepare the transaction data
const config = await prepareWriteContract({
address: Addresses.SIMPLE_MULTIPLIER_ADDR,
abi: abiPath.abi,
functionName: 'submitProof',
args: [proof, publicSignals]
functionName: "submitProof",
args: [proof, publicSignals],
});

// Execute the transaction
Expand All @@ -19,4 +22,4 @@ export const executeTransaction = async (proof: any, publicSignals: Array<string
// Wait for the transaction block to be mined
const txResult = await writeResult.wait();
return txResult;
}
};
46 changes: 31 additions & 15 deletions src/lib/generateProof.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,54 @@
import path from "path";
// @ts-ignore
import * as snarkjs from 'snarkjs';
import * as snarkjs from "snarkjs";

export const generateProof = async (input0: number, input1: number): Promise<any> => {
export const generateProof = async (
input0: number,
input1: number
): Promise<any> => {
console.log(`Generating vote proof with inputs: ${input0}, ${input1}`);

// We need to have the naming scheme and shape of the inputs match the .circom file
const inputs = {
in: [input0, input1],
}
};

// Paths to the .wasm file and proving key
const wasmPath = path.join(process.cwd(), 'circuits/build/simple_multiplier_js/simple_multiplier.wasm');
const provingKeyPath = path.join(process.cwd(), 'circuits/build/proving_key.zkey')
const wasmPath = path.join(
process.cwd(),
"circuits/build/simple_multiplier_js/simple_multiplier.wasm"
);
const provingKeyPath = path.join(
process.cwd(),
"circuits/build/proving_key.zkey"
);

try {
// Generate a proof of the circuit and create a structure for the output signals
const { proof, publicSignals } = await snarkjs.plonk.fullProve(inputs, wasmPath, provingKeyPath);
const { proof, publicSignals } = await snarkjs.plonk.fullProve(
inputs,
wasmPath,
provingKeyPath
);

// Convert the data into Solidity calldata that can be sent as a transaction
const calldataBlob = await snarkjs.plonk.exportSolidityCallData(proof, publicSignals);
const calldata = calldataBlob.split(',');
const calldataBlob = await snarkjs.plonk.exportSolidityCallData(
proof,
publicSignals
);
const calldata = calldataBlob.split(",");

console.log(calldata);

return {
proof: calldata[0],
proof: calldata[0],
publicSignals: JSON.parse(calldata[1]),
}
};
} catch (err) {
console.log(`Error:`, err)
console.log(`Error:`, err);
return {
proof: "",
proof: "",
publicSignals: [],
}
};
}
}
};
24 changes: 12 additions & 12 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import '@/styles/globals.css'
import { WagmiConfig, createClient, configureChains, goerli } from 'wagmi'
import { publicProvider } from 'wagmi/providers/public'
import type { AppProps } from 'next/app'
import { MantineProvider } from '@mantine/core'
import { Notifications } from '@mantine/notifications';
import "@/styles/globals.css";
import { WagmiConfig, createClient, configureChains, goerli } from "wagmi";
import { publicProvider } from "wagmi/providers/public";
import type { AppProps } from "next/app";
import { MantineProvider } from "@mantine/core";
import { Notifications } from "@mantine/notifications";

// We'll just be using Goerli testnet for now
const { chains, provider, webSocketProvider } = configureChains(
[goerli],
[publicProvider()],
)
[publicProvider()]
);

const client = createClient({
autoConnect: true,
provider,
webSocketProvider,
})
});

export default function App({ Component, pageProps }: AppProps) {
// We'll be using Wagmi sending our transaction and Mantine for CSS
// We'll be using Wagmi sending our transaction and Mantine for CSS
// and notifications
return (
<WagmiConfig client={client}>
Expand All @@ -27,5 +27,5 @@ export default function App({ Component, pageProps }: AppProps) {
<Component {...pageProps} />
</MantineProvider>
</WagmiConfig>
)
);
}
4 changes: 2 additions & 2 deletions src/pages/_document.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Html, Head, Main, NextScript } from 'next/document'
import { Html, Head, Main, NextScript } from "next/document";

export default function Document() {
return (
Expand All @@ -9,5 +9,5 @@ export default function Document() {
<NextScript />
</body>
</Html>
)
);
}
22 changes: 13 additions & 9 deletions src/pages/api/generate_proof.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
import { generateProof } from '@/lib/generateProof';
import type { NextApiRequest, NextApiResponse } from 'next'
import { generateProof } from "@/lib/generateProof";
import type { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
res: NextApiResponse
) {
const body = req?.body;
if (body === undefined) {
return res.status(403).json({error: "Request has no body"});
return res.status(403).json({ error: "Request has no body" });
}
console.log(body);
// console.log(body);

const input0 = parseInt(body.input0);
const input1 = parseInt(body.input1);

if (input0 === undefined || Number.isNaN(input0)
|| input1 === undefined || Number.isNaN(input1)) {
return res.status(403).json({error: "Invalid inputs"});
if (
input0 === undefined ||
Number.isNaN(input0) ||
input1 === undefined ||
Number.isNaN(input1)
) {
return res.status(403).json({ error: "Invalid inputs" });
}
const proof = await generateProof(input0, input1);

if (proof.proof === "") {
return res.status(403).json({error: "Proving failed"});
return res.status(403).json({ error: "Proving failed" });
}

res.setHeader("Content-Type", "text/json");
Expand Down
77 changes: 41 additions & 36 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
import Head from 'next/head'
import Link from 'next/link';
import { useState } from 'react';
import { Stack, Text, Title, Grid, Input, Button, Group, Space } from '@mantine/core'
import axios, { AxiosRequestConfig } from 'axios';
import { useAccount } from 'wagmi';
import Head from "next/head";
import Link from "next/link";
import { useState } from "react";
import {
Stack,
Text,
Title,
Grid,
Input,
Button,
Group,
Space,
} from "@mantine/core";
import axios, { AxiosRequestConfig } from "axios";
import { useAccount } from "wagmi";
import { notifications } from "@mantine/notifications";
import { ConnectWalletButton } from '@/components/ConnectWalletButton';
import { executeTransaction } from '@/lib/executeTransaction';
import { ConnectWalletButton } from "@/components/ConnectWalletButton";
import { executeTransaction } from "@/lib/executeTransaction";

export default function Home() {
const [input0, setInput0] = useState("");
const [input1, setInput1] = useState("");
const { isConnected } = useAccount();

const handleGenerateProofSendTransaction = async (e: any) => {
e.preventDefault();
// We will send an HTTP request with our inputs to our next.js backend to

// We will send an HTTP request with our inputs to our next.js backend to
// request a proof to be generated.
const data = {
input0,
input1,
}
};
const config: AxiosRequestConfig = {
headers: {
"Content-Type": "application/json",
}
}
},
};

// Send the HTTP request
try {
Expand Down Expand Up @@ -56,17 +65,15 @@ export default function Home() {
color: "red",
});
}
}
};

// Only allow submit if the user first connects their wallet
const renderSubmitButton = () => {
if (!isConnected) {
return <ConnectWalletButton />
return <ConnectWalletButton />;
}
return (
<Button type="submit">Generate Proof & Send Transaction</Button>
)
}
return <Button type="submit">Generate Proof & Send Transaction</Button>;
};

return (
<>
Expand All @@ -77,51 +84,49 @@ export default function Home() {
<Stack justify="center" align="center" w="100vw" h="100vh" spacing={0}>
<Stack align="center" spacing={0}>
<Group w="96vw" h="10vh" position="apart" align="center">
<Title order={3}>
ZK Simple Multiplier
</Title>
<Title order={3}>ZK Simple Multiplier</Title>
<ConnectWalletButton />
</Group>
<Grid align="center" justify="center" mih="80vh">
<Grid.Col sm={8} md={6} lg={4}>
<Text>
{"Input two numbers between 0 and 5, inclusive. The two numbers must \
{
"Input two numbers between 0 and 5, inclusive. The two numbers must \
not be equal. We'll generate a ZK proof locally in the browser, and \
only the proof will be sent to the blockchain so that no one \
watching the blockchain will know the two numbers."}
watching the blockchain will know the two numbers."
}
</Text>
<Space h={20} />
<form onSubmit={handleGenerateProofSendTransaction}>
<Stack spacing="sm">
<Input.Wrapper label="Input 0">
<Input
placeholder="Number between 0 and 5"
value={input0}
<Input
placeholder="Number between 0 and 5"
value={input0}
onChange={(e) => setInput0(e.currentTarget.value)}
/>
</Input.Wrapper>
<Input.Wrapper label="Input 1">
<Input
placeholder="Number between 0 and 5"
value={input1}
<Input
placeholder="Number between 0 and 5"
value={input1}
onChange={(e) => setInput1(e.currentTarget.value)}
/>
</Input.Wrapper>
<Space h={10} />
{ renderSubmitButton() }
{renderSubmitButton()}
</Stack>
</form>
</Grid.Col>
</Grid>
<Group w="96vw" h="10vh" position="center" align="center">
<Link href="https://medium.com/@yujiangtham/writing-a-zero-knowledge-dapp-fd7f936e2d43">
<Text>
Created using this tutorial!
</Text>
<Text>Created using this tutorial!</Text>
</Link>
</Group>
</Stack>
</Stack>
</>
)
);
}
5 changes: 3 additions & 2 deletions src/shared/addresses.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const Addresses = {
SIMPLE_MULTIPLIER_ADDR: "0xfa61b7aa324341e71f2252caa1d0ec454360dc6e" as `0x${string}`,
}
SIMPLE_MULTIPLIER_ADDR:
"0xfa61b7aa324341e71f2252caa1d0ec454360dc6e" as `0x${string}`,
};
4 changes: 2 additions & 2 deletions src/styles/Home.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@

.center::before,
.center::after {
content: '';
content: "";
left: 50%;
position: absolute;
filter: blur(45px);
Expand Down Expand Up @@ -125,7 +125,7 @@

.thirteen::before,
.thirteen::after {
content: '';
content: "";
position: absolute;
z-index: -1;
}
Expand Down
6 changes: 3 additions & 3 deletions src/styles/globals.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
:root {
--max-width: 1100px;
--border-radius: 12px;
--font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
--font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono",
"Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro",
"Fira Mono", "Droid Sans Mono", "Courier New", monospace;

--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
Expand Down
Loading