add tutorial

This commit is contained in:
Wanseob Lim
2022-04-23 11:03:41 +02:00
parent 7df7e0f6de
commit 40533596a9
2 changed files with 674 additions and 0 deletions

View File

@@ -39,3 +39,7 @@
yarn workspace app test
```
## Example: EdDSA signature rollup
Let's compress EdDSA signatures into one zk proof! Go to [tutorial!](./TUTORIAL.md)

670
TUTORIAL.md Normal file
View File

@@ -0,0 +1,670 @@
# Tutorial: EdDSA signature rollup
## Update circuits and test them
1. Update your circuit
```diff
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
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:
```diff --git a/circuits/test/main.test.ts b/circuits/test/main.test.ts
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!
```shell
yarn workspace circuits build
yarn workspace circuits test
```
## Update the contract
1. Update the smart contract
```diff
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);
}
/**
@@ -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
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!
```shell
yarn workspace contracts build
yarn workspace contracts test
```
## Update the UI
1. Update hooks
```diff
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 };
};
```
1. Add RegisterKey.tsx component
```typescript
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;
```
1. Update GenZKP.tsx
```diff
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);
}
```
1. Update SendTx.tsx
```diff
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,
});
```
1. Update App.tsx
```diff
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>
)}
```
1. Update Viewer.tsx
```diff
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>;
}
```