Skip to content

Commit e61df72

Browse files
committed
feat(sdk): allow to reuse inclusion proof for joinPoll
1 parent 480c804 commit e61df72

File tree

9 files changed

+320
-71
lines changed

9 files changed

+320
-71
lines changed

packages/sdk/ts/browser/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ export * from "../trees";
66
export * from "../vote";
77
export * from "../maciKeys";
88
export { getSignedupUserData, signup, hasUserSignedUp } from "../user/signup";
9-
export { getJoinedUserData, hasUserJoinedPoll } from "../user/utils";
9+
export {
10+
getJoinedUserData,
11+
hasUserJoinedPoll,
12+
genMaciStateTree,
13+
getPollJoiningCircuitEvents,
14+
joiningCircuitInputs,
15+
genMaciStateTreeWithEndKey,
16+
} from "../user/utils";
1017

1118
export * from "./joinPoll";
1219

packages/sdk/ts/browser/joinPoll.ts

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,24 @@ import { MACI__factory as MACIFactory, Poll__factory as PollFactory } from "@mac
33
import { poseidon } from "@maci-protocol/crypto";
44
import { Keypair, PrivateKey } from "@maci-protocol/domainobjs";
55

6-
import type { IJoinPollArgs, IJoinPollData } from "../user/types";
6+
import type { IJoinPollBrowserArgs, IJoinPollData } from "../user/types";
7+
import type { TCircuitInputs } from "../utils/types";
78

8-
import {
9-
getPollJoiningCircuitEvents,
10-
getPollJoiningCircuitInputsFromStateFile,
11-
hasUserJoinedPoll,
12-
} from "../user/utils";
9+
import { getPollJoiningCircuitEvents, hasUserJoinedPoll, joiningCircuitInputs } from "../user/utils";
1310
import { contractExists } from "../utils/contracts";
14-
import { TCircuitInputs } from "../utils/types";
1511

1612
import { generateProofSnarkjs, formatProofForVerifierContract } from "./utils";
1713

1814
/**
1915
* Join Poll user to the Poll contract
16+
* @dev This version is optimised to work on browsers
17+
* @dev It uses WASM + accepts already created inclusion proofs
2018
* @param {IJoinPollArgs} args - The arguments for the join poll command
2119
* @returns {IJoinPollData} The poll state index of the joined user and transaction hash
2220
*/
2321
export const joinPoll = async ({
2422
maciAddress,
2523
privateKey,
26-
stateFile,
2724
pollId,
2825
signer,
2926
startBlock,
@@ -33,7 +30,9 @@ export const joinPoll = async ({
3330
pollWasm,
3431
sgDataArg,
3532
ivcpDataArg,
36-
}: IJoinPollArgs): Promise<IJoinPollData> => {
33+
inclusionProof,
34+
useLatestStateIndex,
35+
}: IJoinPollBrowserArgs): Promise<IJoinPollData> => {
3736
const validContract = await contractExists(signer.provider!, maciAddress);
3837

3938
if (!validContract) {
@@ -71,15 +70,12 @@ export const joinPoll = async ({
7170
// get the state index from the MACI contract
7271
const stateIndex = await maciContract.getStateIndex(userMaciPublicKey.hash()).catch(() => -1n);
7372

73+
const stateTreeDepth = await maciContract.stateTreeDepth();
74+
7475
let circuitInputs: TCircuitInputs;
7576

76-
if (stateFile) {
77-
circuitInputs = await getPollJoiningCircuitInputsFromStateFile({
78-
stateFile,
79-
pollId,
80-
stateIndex,
81-
userMaciPrivateKey,
82-
});
77+
if (inclusionProof) {
78+
circuitInputs = joiningCircuitInputs(inclusionProof, stateTreeDepth, userMaciPrivateKey, userMaciPublicKey, pollId);
8379
} else {
8480
circuitInputs = await getPollJoiningCircuitEvents({
8581
maciContract,
@@ -93,7 +89,9 @@ export const joinPoll = async ({
9389
});
9490
}
9591

96-
const currentStateRootIndex = Number.parseInt((await maciContract.totalSignups()).toString(), 10) - 1;
92+
const stateRootIndex = useLatestStateIndex
93+
? Number.parseInt((await maciContract.totalSignups()).toString(), 10) - 1
94+
: stateIndex;
9795

9896
// generate the proof for this batch
9997
const { proof } = await generateProofSnarkjs({
@@ -106,7 +104,7 @@ export const joinPoll = async ({
106104
const tx = await pollContract.joinPoll(
107105
nullifier,
108106
userMaciPublicKey.asContractParam(),
109-
currentStateRootIndex,
107+
stateRootIndex,
110108
formatProofForVerifierContract(proof),
111109
sgDataArg,
112110
ivcpDataArg,

packages/sdk/ts/trees/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { generateSignUpTree } from "./stateTree";
2-
export type { IGenerateSignUpTreeArgs, IGenerateSignUpTree } from "./types";
1+
export { generateSignUpTree, generateSignUpTreeWithEndKey } from "./stateTree";
2+
export type { IGenerateSignUpTreeArgs, IGenerateSignUpTree, IGenerateSignUpTreeWithEndKeyArgs } from "./types";

packages/sdk/ts/trees/stateTree.ts

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import { hashLeanIMT, hashLeftRight, PAD_KEY_HASH } from "@maci-protocol/crypto"
44
import { PublicKey } from "@maci-protocol/domainobjs";
55
import { LeanIMT, LeanIMTHashFunction } from "@zk-kit/lean-imt";
66

7-
import assert from "assert";
8-
9-
import type { IGenerateSignUpTreeArgs, IGenerateSignUpTree } from "./types";
7+
import type { IGenerateSignUpTreeArgs, IGenerateSignUpTree, IGenerateSignUpTreeWithEndKeyArgs } from "./types";
108

119
import { sleep } from "../utils/utils";
1210

@@ -44,7 +42,6 @@ export const generateSignUpTree = async ({
4442
// eslint-disable-next-line no-await-in-loop
4543
const signUpLogs = await maciContract.queryFilter(maciContract.filters.SignUp(), i, toBlock);
4644
signUpLogs.forEach((event) => {
47-
assert(!!event);
4845
const publicKeyX = event.args._userPublicKeyX;
4946
const publicKeyY = event.args._userPublicKeyY;
5047

@@ -65,3 +62,69 @@ export const generateSignUpTree = async ({
6562
publicKeys,
6663
};
6764
};
65+
66+
/**
67+
* Generate a State tree object from the events of a MACI smart contracts
68+
* @param provider - the ethereum provider
69+
* @param address - the address of the MACI contract
70+
* @param fromBlock - the block number from which to start fetching events
71+
* @param blocksPerRequest - the number of blocks to fetch in each request
72+
* @param endBlock - the block number at which to stop fetching events
73+
* @param sleepAmount - the amount of time to sleep between each request
74+
* @returns State tree
75+
*/
76+
export const generateSignUpTreeWithEndKey = async ({
77+
provider,
78+
address,
79+
fromBlock = 0,
80+
blocksPerRequest = 50,
81+
endBlock,
82+
sleepAmount,
83+
userPublicKey,
84+
}: IGenerateSignUpTreeWithEndKeyArgs): Promise<IGenerateSignUpTree> => {
85+
const lastBlock = endBlock || (await provider.getBlockNumber());
86+
87+
const maciContract = MACIFactory.connect(address, provider);
88+
const signUpTree = new LeanIMT(hashLeanIMT as LeanIMTHashFunction);
89+
signUpTree.insert(PAD_KEY_HASH);
90+
const publicKeys: PublicKey[] = [];
91+
92+
// Fetch event logs in batches (lastBlock inclusive)
93+
for (let i = fromBlock; i <= lastBlock; i += blocksPerRequest + 1) {
94+
// the last block batch will be either current iteration block + blockPerRequest
95+
// or the end block if it is set
96+
const toBlock = i + blocksPerRequest >= lastBlock ? lastBlock : i + blocksPerRequest;
97+
98+
// eslint-disable-next-line no-await-in-loop
99+
const signUpLogs = await maciContract.queryFilter(maciContract.filters.SignUp(), i, toBlock);
100+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
101+
for (let j = 0; j < signUpLogs.length; j += 1) {
102+
const event = signUpLogs[j];
103+
const publicKeyX = event.args._userPublicKeyX;
104+
const publicKeyY = event.args._userPublicKeyY;
105+
106+
const publicKey = new PublicKey([publicKeyX, publicKeyY]);
107+
108+
publicKeys.push(publicKey);
109+
signUpTree.insert(hashLeftRight(publicKeyX, publicKeyY));
110+
111+
// early return cause we found the user
112+
if (publicKey.equals(userPublicKey)) {
113+
return {
114+
signUpTree,
115+
publicKeys,
116+
};
117+
}
118+
}
119+
120+
if (sleepAmount) {
121+
// eslint-disable-next-line no-await-in-loop
122+
await sleep(sleepAmount);
123+
}
124+
}
125+
126+
return {
127+
signUpTree,
128+
publicKeys,
129+
};
130+
};

packages/sdk/ts/trees/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ export interface IGenerateSignUpTreeArgs {
3737
sleepAmount?: number;
3838
}
3939

40+
/**
41+
* An interface that represents arguments of generation sign up tree which stops fetching at a given public key
42+
*/
43+
export interface IGenerateSignUpTreeWithEndKeyArgs extends IGenerateSignUpTreeArgs {
44+
/**
45+
* The public key of the user
46+
*/
47+
userPublicKey: PublicKey;
48+
}
49+
4050
/**
4151
* An interface that represents sign up tree and state leaves
4252
*/

packages/sdk/ts/user/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
export { joinPoll } from "./joinPoll";
22
export { getSignedupUserData, signup, hasUserSignedUp } from "./signup";
3-
export { getJoinedUserData, hasUserJoinedPoll } from "./utils";
3+
export {
4+
getJoinedUserData,
5+
hasUserJoinedPoll,
6+
genMaciStateTree,
7+
genMaciStateTreeWithEndKey,
8+
getPollJoiningCircuitEvents,
9+
joiningCircuitInputs,
10+
} from "./utils";
411
export type {
512
IJoinedUserArgs,
613
IIsRegisteredUser,
@@ -18,4 +25,6 @@ export type {
1825
IParseSignupEventsArgs,
1926
ISignupData,
2027
IHasUserSignedUpArgs,
28+
IGenMaciStateTreeArgs,
29+
IGenMaciStateTreeWithEndKeyArgs,
2130
} from "./types";

packages/sdk/ts/user/types.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { LeanIMTMerkleProof } from "@zk-kit/lean-imt";
2+
13
import type { MACI, Poll } from "@maci-protocol/contracts/typechain-types";
24
import type { PrivateKey, PublicKey } from "@maci-protocol/domainobjs";
35
import type { Signer } from "ethers";
@@ -354,6 +356,21 @@ export interface IJoinPollArgs {
354356
ivcpDataArg: string;
355357
}
356358

359+
/**
360+
* Interface for the arguments to the joinPoll command for the browser
361+
*/
362+
export interface IJoinPollBrowserArgs extends IJoinPollArgs {
363+
/**
364+
* Whether to use of not the latest state index
365+
*/
366+
useLatestStateIndex?: boolean;
367+
368+
/**
369+
* The inclusion proof
370+
*/
371+
inclusionProof?: LeanIMTMerkleProof;
372+
}
373+
357374
/**
358375
* Interface for the return data to the joinPoll command
359376
*/
@@ -401,6 +418,46 @@ export interface IIsNullifierOnChainArgs {
401418
signer: Signer;
402419
}
403420

421+
/**
422+
* Arguments for IGenMaciStateTreeArgs
423+
*/
424+
export interface IGenMaciStateTreeArgs {
425+
/**
426+
* The MACI contract
427+
*/
428+
maciContract: MACI;
429+
430+
/**
431+
* The signer
432+
*/
433+
signer: Signer;
434+
435+
/**
436+
* The start block
437+
*/
438+
startBlock?: number;
439+
440+
/**
441+
* The end block
442+
*/
443+
endBlock?: number;
444+
445+
/**
446+
* The blocks per batch
447+
*/
448+
blocksPerBatch?: number;
449+
}
450+
451+
/**
452+
* Arguments for IGenMaciStateTreeWithEndKeyArgs
453+
*/
454+
export interface IGenMaciStateTreeWithEndKeyArgs extends IGenMaciStateTreeArgs {
455+
/**
456+
* The public key of the user
457+
*/
458+
userPublicKey: PublicKey;
459+
}
460+
404461
/**
405462
* Arguments for getPollJoiningCircuitEvents
406463
*/

0 commit comments

Comments
 (0)