-
Update your circuit
diff --git a/circuits/circuits/main.circom b/circuits/circuits/main.circom index bc30fa5..6ee8b4a 100644 --- a/circuits/circuits/main.circom +++ b/circuits/circuits/main.circom @@ -1,25 +1,29 @@ pragma circom 2.0.0; include "../../node_modules/circomlib/circuits/eddsaposeidon.circom"; -template Sample() { +template Sample(n) { // public signal input M; - signal input Ax; - signal input Ay; + signal input Ax[n]; + signal input Ay[n]; // private - signal input S; - signal input R8x; - signal input R8y; + signal input S[n]; + signal input R8x[n]; + signal input R8y[n]; - component verifier = EdDSAPoseidonVerifier(); - verifier.enabled <== 1; - verifier.Ax <== Ax; - verifier.Ay <== Ay; - verifier.S <== S; - verifier.R8x <== R8x; - verifier.R8y <== R8y; - verifier.M <== M; + component verifier[n]; + for (var i = 0; i < n; i++) { + verifier[i] = EdDSAPoseidonVerifier(); + verifier[i].enabled <== 1; + verifier[i].Ax <== Ax[i]; + verifier[i].Ay <== Ay[i]; + verifier[i].S <== S[i]; + verifier[i].R8x <== R8x[i]; + verifier[i].R8y <== R8y[i]; + verifier[i].M <== M; + } + } -component main { public [M, Ax, Ay] } = Sample(); +component main { public [M, Ax, Ay] } = Sample(3);
-
Update the client code and the test cases
Client code:
diff --git a/circuits/src/client.ts b/circuits/src/client.ts index 946bba7..7270b2c 100644 --- a/circuits/src/client.ts +++ b/circuits/src/client.ts @@ -55,11 +55,11 @@ export class ZKPClient { R8y, }: { M: bigint; - Ax: bigint; - Ay: bigint; - S: bigint; - R8x: bigint; - R8y: bigint; + Ax: bigint[]; + Ay: bigint[]; + S: bigint[]; + R8x: bigint[]; + R8y: bigint[]; }): Promise<Proof> { const inputs = { M,
Test codes:
index f85fa9d..4a8a198 100644 --- a/circuits/test/main.test.ts +++ b/circuits/test/main.test.ts @@ -11,7 +11,7 @@ describe("Test zkp circuit and scripts", function () { const privKey = "0xABCDEF12ABCDEF12ABCDEF12ABCDEF12ABCDEF12ABCDEF12ABCDEF12ABCDEF12"; let client: ZKPClient; - let eddsa: EdDSA; + let accounts: [EdDSA, EdDSA, EdDSA]; beforeEach(async () => { const wasm = fs.readFileSync( path.join(__dirname, "../../circuits/zk/circuits/main_js/main.wasm") @@ -20,21 +20,31 @@ describe("Test zkp circuit and scripts", function () { path.join(__dirname, "../../circuits/zk/zkeys/main.zkey") ); client = await new ZKPClient().init(wasm, zkey); - eddsa = await new EdDSA(privKey).init(); + accounts = await Promise.all([ + new EdDSA("0x1111").init(), + new EdDSA("0x2222").init(), + new EdDSA("0x3333").init(), + ]); }); it("Should able to prove and verify the zkp", async function () { const message = BigNumber.from( "0xABCDEF00ABCDEF00ABCDEF00ABCDEF00ABCDEF00ABCDEF00ABCDEF00ABCDEF00" ); - const signature = await eddsa.sign(message); - expect(await eddsa.verify(message, signature, eddsa.pubKey)).to.eq(true); + const signatures = await Promise.all( + accounts.map((acc) => acc.sign(message)) + ); + const result = await Promise.all( + accounts.map((acc, i) => acc.verify(message, signatures[i], acc.pubKey)) + ); + result.map((res) => expect(res).to.eq(true)); + const F = accounts[0].babyjub.F; const proof = await client.prove({ M: message.toBigInt(), - Ax: eddsa.scalarPubKey[0], - Ay: eddsa.scalarPubKey[1], - S: signature.S, - R8x: eddsa.babyjub.F.toObject(signature.R8[0]), - R8y: eddsa.babyjub.F.toObject(signature.R8[1]), + Ax: accounts.map((acc) => acc.scalarPubKey[0]), + Ay: accounts.map((acc) => acc.scalarPubKey[1]), + S: signatures.map((sig) => sig.S), + R8x: signatures.map((sig) => F.toObject(sig.R8[0])), + R8y: signatures.map((sig) => F.toObject(sig.R8[1])), }); expect(proof).not.to.eq(undefined); });
-
Run the test!
yarn workspace circuits build yarn workspace circuits test
-
Update the smart contract
diff --git a/contracts/contracts/ZkApp.sol b/contracts/contracts/ZkApp.sol index 5168fb7..fcf77a4 100644 --- a/contracts/contracts/ZkApp.sol +++ b/contracts/contracts/ZkApp.sol @@ -7,7 +7,7 @@ interface IVerifier { uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, - uint256[3] memory input + uint256[7] memory input ) external view returns (bool r); } @@ -19,20 +19,35 @@ contract ZkApp { } address public immutable verifier; - uint256[3][] public records; // just a sample var + uint256[2][] public pubKeys; + uint256[] public signedMessages; constructor(address verifier_) { verifier = verifier_; } + function registerKey(uint256[2] memory key) public { + require(pubKeys.length < 3, "Multisig for 3"); + pubKeys.push(key); + } + /** * @dev This is the sample function */ - function record(uint256[3] memory publicSignals, Proof memory proof) + function recordSignedMessage(uint256 message, Proof memory proof) public { + require(pubKeys.length == 3, "Key registration is not done yet."); + uint256[7] memory publicSignals; + publicSignals[0] = message; + publicSignals[1] = pubKeys[0][0]; + publicSignals[2] = pubKeys[1][0]; + publicSignals[3] = pubKeys[2][0]; + publicSignals[4] = pubKeys[0][1]; + publicSignals[5] = pubKeys[1][1]; + publicSignals[6] = pubKeys[2][1]; require(verify(publicSignals, proof), "SNARK verification failed"); - records.push(publicSignals); + signedMessages.push(message); } /** * Please adjust the IVerifier.sol and the array length of publicSignals */ - function verify(uint256[3] memory publicSignals, Proof memory proof) + function verify(uint256[7] memory publicSignals, Proof memory proof) public view returns (bool) @@ -52,7 +67,7 @@ contract ZkApp { return result; } - function totalRecords() public view returns (uint256) { - return records.length; + function totalSignedMessages() public view returns (uint256) { + return signedMessages.length; } }
-
Update the test code
diff --git a/contracts/test/ZkApp.test.ts b/contracts/test/ZkApp.test.ts index fd7b69a..07e7875 100644 --- a/contracts/test/ZkApp.test.ts +++ b/contracts/test/ZkApp.test.ts @@ -17,13 +17,15 @@ describe("Test ZKP contract", function () { let zkApp: ZkApp; let deployer: SignerWithAddress; let client: ZKPClient; - let eddsa: EdDSA; + let accounts: [EdDSA, EdDSA, EdDSA]; this.beforeEach(async () => { [deployer] = await ethers.getSigners(); verifier = await new Verifier__factory(deployer).deploy(); - eddsa = await new EdDSA( - "0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD" - ).init(); + accounts = await Promise.all([ + new EdDSA("0x1111").init(), + new EdDSA("0x2222").init(), + new EdDSA("0x3333").init(), + ]); zkApp = await new ZkApp__factory(deployer).deploy(verifier.address); client = await new ZKPClient().init( fs.readFileSync( @@ -33,21 +35,24 @@ describe("Test ZKP contract", function () { ); }); it("Should able to create a zkp and verify them", async function () { - const msg = BigNumber.from("0xabcd"); - const signature = await eddsa.sign(msg); + const message = BigNumber.from("0xabcd"); + const signatures = await Promise.all( + accounts.map((acc) => acc.sign(message)) + ); + const F = accounts[0].babyjub.F; const proof = await client.prove({ - M: msg.toBigInt(), - Ax: eddsa.scalarPubKey[0], - Ay: eddsa.scalarPubKey[1], - S: signature.S, - R8x: eddsa.babyjub.F.toObject(signature.R8[0]), - R8y: eddsa.babyjub.F.toObject(signature.R8[1]), + M: message.toBigInt(), + Ax: accounts.map((acc) => acc.scalarPubKey[0]), + Ay: accounts.map((acc) => acc.scalarPubKey[1]), + S: signatures.map((sig) => sig.S), + R8x: signatures.map((sig) => F.toObject(sig.R8[0])), + R8y: signatures.map((sig) => F.toObject(sig.R8[1])), }); - expect( - await zkApp.verify( - [msg, eddsa.scalarPubKey[0], eddsa.scalarPubKey[1]], - proof - ) - ).to.eq(true); + await zkApp.registerKey(accounts[0].scalarPubKey); + await zkApp.registerKey(accounts[1].scalarPubKey); + await zkApp.registerKey(accounts[2].scalarPubKey); + expect(await zkApp.totalSignedMessages()).to.eq(0); + await zkApp.recordSignedMessage(message, proof); + expect(await zkApp.totalSignedMessages()).to.eq(1); }); });
-
Build and run test!
yarn workspace contracts build yarn workspace contracts test
-
Update hooks
diff --git a/app/src/hooks/useContract.ts b/app/src/hooks/useContract.ts index 38247b6..229e330 100644 --- a/app/src/hooks/useContract.ts +++ b/app/src/hooks/useContract.ts @@ -37,24 +37,24 @@ export const useVerifier = (address: string) => { /** * @dev This fetches data every block when the component is mounted */ -export const useTotalRecords = (address: string) => { +export const useTotalSignedMessages = (address: string) => { const zkApp = useZKApp(address); const [totalRecords, setTotalRecords] = useState<BigNumber>(); const { library, chainId } = useEthers(); - const fetchTotalRecords = useCallback(async () => { + const fetchTotalSignedMessages = useCallback(async () => { if (!zkApp) return; - const _totalRecords = await zkApp.totalRecords(); + const _totalRecords = await zkApp.totalSignedMessages(); setTotalRecords(_totalRecords); }, [zkApp]); useEffect(() => { - fetchTotalRecords(); - library?.on("block", fetchTotalRecords); + fetchTotalSignedMessages(); + library?.on("block", fetchTotalSignedMessages); return () => { - library?.off("block", fetchTotalRecords); + library?.off("block", fetchTotalSignedMessages); }; - }, [address, library, chainId, fetchTotalRecords]); + }, [address, library, chainId, fetchTotalSignedMessages]); return totalRecords; }; @@ -70,17 +70,17 @@ export enum TxState { /** * @dev Contract interfaction example with ZKP */ -export const useRecord = (address: string) => { +export const useRecordSignedmessage = (address: string) => { const zkApp = useZKApp(address); const [txState, setTxState] = useState<TxState>(TxState.NONE); const { library, chainId, account } = useEthers(); - const record = useCallback( + const recordSignedMessage = useCallback( async ({ publicSignals, proof, }: { - publicSignals: [BigNumberish, BigNumberish, BigNumberish]; + publicSignals: [BigNumberish]; proof: { a: [BigNumberish, BigNumberish]; b: [[BigNumberish, BigNumberish], [BigNumberish, BigNumberish]]; @@ -92,7 +92,7 @@ export const useRecord = (address: string) => { const signer = library.getSigner(account); zkApp .connect(signer) - .record(publicSignals, proof) + .recordSignedMessage(publicSignals[0], proof) .then((tx) => { setTxState(TxState.PENDING); tx.wait() @@ -103,5 +103,34 @@ export const useRecord = (address: string) => { }, [library, chainId, account, zkApp] ); - return { txState, record: account ? record : undefined }; + return { + txState, + recordSignedMessage: account ? recordSignedMessage : undefined, + }; +}; + +export const useRegisterKey = (address: string) => { + const zkApp = useZKApp(address); + const [txState, setTxState] = useState<TxState>(TxState.NONE); + const { library, chainId, account } = useEthers(); + + const registerKey = useCallback( + async ({ pubKey }: { pubKey: [BigNumberish, BigNumberish] }) => { + if (!zkApp || !account) return; + if (!library) return; + const signer = library.getSigner(account); + zkApp + .connect(signer) + .registerKey(pubKey) + .then((tx) => { + setTxState(TxState.PENDING); + tx.wait() + .then(() => setTxState(TxState.CONFIRMED)) + .catch(() => setTxState(TxState.FAILED)); + }) + .catch(() => setTxState(TxState.CANCELLED)); + }, + [library, chainId, account, zkApp] + ); + return { txState, registerKey: account ? registerKey : undefined }; };
-
Add RegisterKey.tsx component
import { useRegisterKey } from "../hooks/useContract"; function RegisterKey({ address, pubKey, }: { address: string; pubKey?: [bigint, bigint]; }) { const { txState, registerKey } = useRegisterKey(address); return ( <div> <p>Tx state: {txState}</p> <button disabled={!registerKey || !pubKey} onClick={() => { if (!pubKey) alert("Pub key is not prepared"); else if (!registerKey) alert("Wallet is not connected"); else { registerKey({ pubKey, }); } }} > Register pubkey {pubKey} </button> </div> ); } export default RegisterKey;
-
Update GenZKP.tsx
diff --git a/app/src/components/GenZKP.tsx b/app/src/components/GenZKP.tsx index 38ae91e..800d80e 100644 --- a/app/src/components/GenZKP.tsx +++ b/app/src/components/GenZKP.tsx @@ -3,34 +3,38 @@ import { EdDSASignature } from "circuits/src/eddsa"; import useCircuit from "../hooks/useCircuit"; function GenZKP({ - signature, + signatures, message, - pubKey, + pubKeys, onResult, }: { message: bigint; - pubKey?: [bigint, bigint]; - signature?: EdDSASignature; + pubKeys?: [bigint, bigint][]; + signatures?: EdDSASignature[]; onResult: (proof: Proof) => void; }) { const { client } = useCircuit(); return ( <div> <button - disabled={!client || !pubKey || !signature} + disabled={!client || !pubKeys || !signatures} onClick={async () => { if (!client) alert("Client is not ready"); - else if (!pubKey) alert("EdDSA pubkey is not ready"); - else if (!signature) alert("EdDSA signature is not ready"); + else if (!pubKeys) alert("EdDSA pubkey is not ready"); + else if (!signatures) alert("EdDSA signature is not ready"); else { client .prove({ M: message, - Ax: pubKey[0], - Ay: pubKey[1], - S: signature.S, - R8x: client.babyjub.F.toObject(signature.R8[0]), - R8y: client.babyjub.F.toObject(signature.R8[1]), + Ax: pubKeys.map((key) => key[0]), + Ay: pubKeys.map((key) => key[1]), + S: signatures.map((s) => s.S), + R8x: signatures.map((signature) => + client.babyjub.F.toObject(signature.R8[0]) + ), + R8y: signatures.map((signature) => + client.babyjub.F.toObject(signature.R8[1]) + ), }) .then(onResult); }
-
Update SendTx.tsx
diff --git a/app/src/components/SendTx.tsx b/app/src/components/SendTx.tsx index 26f13b3..85fd97f 100644 --- a/app/src/components/SendTx.tsx +++ b/app/src/components/SendTx.tsx @@ -1,5 +1,5 @@ import { Proof } from "circuits"; -import { useRecord } from "../hooks/useContract"; +import { useRecordSignedmessage } from "../hooks/useContract"; function SendTx({ address, @@ -7,24 +7,24 @@ function SendTx({ proof, }: { address: string; - publicSignals?: [bigint, bigint, bigint]; + publicSignals?: [bigint]; proof?: Proof; }) { - const { txState, record } = useRecord(address); + const { txState, recordSignedMessage } = useRecordSignedmessage(address); return ( <div> <p>Tx state: {txState}</p> <p>{!!publicSignals}</p> <p>{!!proof}</p> - <p>{!!record}</p> + <p>{!!recordSignedMessage}</p> <button - disabled={!publicSignals || !proof || !record} + disabled={!publicSignals || !proof || !recordSignedMessage} onClick={() => { if (!publicSignals) alert("Public signals are not ready"); else if (!proof) alert("ZKP is not ready"); - else if (!record) alert("Wallet is not connected"); + else if (!recordSignedMessage) alert("Wallet is not connected"); else { - record({ + recordSignedMessage({ publicSignals, proof, });
-
Update App.tsx
diff --git a/app/src/App.tsx b/app/src/App.tsx index e26709c..4aee875 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -1,8 +1,7 @@ -import { useEthers, useLocalStorage } from "@usedapp/core"; import { Proof } from "circuits"; import { EdDSASignature } from "circuits/src/eddsa"; import { BigNumber } from "ethers"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import "./App.css"; import Connect from "./components/Connect"; import GenZKP from "./components/GenZKP"; @@ -11,16 +10,25 @@ import SignEdDSA from "./components/SignEdDSA"; import Viewer from "./components/Viewer"; import useEdDSA from "./hooks/useEdDSA"; import Deploy from "./components/Deploy"; +import RegisterKey from "./components/RegisterKey"; // const address = process.env["REACT_APP_CONTRACT_ADDRESS"] as string; // if (typeof address !== "string") throw Error("Configure contract address"); const msgToSign = BigNumber.from("0x1234").toBigInt(); function App() { - const { account } = useEthers(); - const privKey = account || undefined; - const { pubKey } = useEdDSA(privKey); - const [eddsaSignature, setEdDSASignature] = useState<EdDSASignature>(); + const privKeys = ["0x1111", "0x2222", "0x3333"]; + const { pubKey: key1 } = useEdDSA(privKeys[0]); + const { pubKey: key2 } = useEdDSA(privKeys[1]); + const { pubKey: key3 } = useEdDSA(privKeys[2]); + const pubKeys = !!key1 && !!key2 && !!key3 ? [key1, key2, key3] : undefined; + const [eddsaSignature1, setEdDSASignature1] = useState<EdDSASignature>(); + const [eddsaSignature2, setEdDSASignature2] = useState<EdDSASignature>(); + const [eddsaSignature3, setEdDSASignature3] = useState<EdDSASignature>(); + const eddsaSignatures = + !!eddsaSignature1 && !!eddsaSignature2 && !!eddsaSignature3 + ? [eddsaSignature1, eddsaSignature2, eddsaSignature3] + : undefined; const [proof, setProof] = useState<Proof>(); const [deployed, setDeployed] = useState<string>(); return ( @@ -32,26 +40,45 @@ function App() { <Deploy onResult={setDeployed} /> <h2>Step 3. Check the data fetch from the smart contract</h2> {deployed ? <Viewer address={deployed} /> : <p>Not deployed</p>} - <h2>Step 4. Prepare zkp signals</h2> + <h2>Step 4. Register EdDSA keys</h2> + {deployed ? ( + <> + <RegisterKey address={deployed} pubKey={key1} /> + <RegisterKey address={deployed} pubKey={key2} /> + <RegisterKey address={deployed} pubKey={key3} /> + </> + ) : ( + <p>Not deployed</p> + )} + <h2>Step 5. Prepare EdDSA signatures</h2> + <SignEdDSA + privKey={privKeys[0]} + message={msgToSign} + onResult={(result) => setEdDSASignature1(result)} + /> + <p>{eddsaSignature1 ? 'signed': 'None'}</p> + <SignEdDSA + privKey={privKeys[1]} + message={msgToSign} + onResult={(result) => setEdDSASignature2(result)} + /> + <p>{eddsaSignature2 ? 'signed': 'None'}</p> <SignEdDSA - privKey={privKey} + privKey={privKeys[2]} message={msgToSign} - onResult={(result) => setEdDSASignature(result)} + onResult={(result) => setEdDSASignature3(result)} /> - <h2>Step 5. Compute a zk proof</h2> + <p>{eddsaSignature3 ? 'signed': 'None'}</p> + <h2>Step 6. Compute a zk proof</h2> <GenZKP message={msgToSign} - pubKey={pubKey} - signature={eddsaSignature} + pubKeys={pubKeys} + signatures={eddsaSignatures} onResult={(result) => setProof(result)} /> - <h2>Step 6. Interact with smart contract using the generated proof</h2> + <h2>Step 7. Interact with smart contract using the generated proof</h2> {deployed ? ( - <SendTx - address={deployed} - publicSignals={pubKey ? [msgToSign, ...pubKey] : undefined} - proof={proof} - /> + <SendTx address={deployed} publicSignals={[msgToSign]} proof={proof} /> ) : ( <p>Not deployed</p> )}
-
Update Viewer.tsx
diff --git a/app/src/components/Viewer.tsx b/app/src/components/Viewer.tsx index 44a29ba..9d2e00f 100644 --- a/app/src/components/Viewer.tsx +++ b/app/src/components/Viewer.tsx @@ -1,7 +1,7 @@ -import { useTotalRecords } from "../hooks/useContract"; +import { useTotalSignedMessages } from "../hooks/useContract"; function Viewer({ address }: { address: string }) { - const totalRecords = useTotalRecords(address); + const totalRecords = useTotalSignedMessages(address); return <div>Total records: {totalRecords?.toString() || "fetching..."}</div>; }