Skip to content

Latest commit

 

History

History
677 lines (627 loc) · 23 KB

TUTORIAL.md

File metadata and controls

677 lines (627 loc) · 23 KB

Tutorial: EdDSA signature rollup

Update circuits and test them

  1. 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);
  2. 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);
       });
  3. Run the test!

    yarn workspace circuits build
    yarn workspace circuits test

Update the contract

  1. 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;
         }
     }
  2. 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);
       });
     });
  3. Build and run test!

    yarn workspace contracts build
    yarn workspace contracts test

Update the UI

  1. 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 };
     };
  2. 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;
  3. 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);
               }
  4. 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,
                 });
  5. 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>
           )}
  6. 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>;
     }