Skip to content

Commit

Permalink
Sumbit votes
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnGuilding committed Aug 28, 2024
1 parent d40d548 commit 5928b77
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 10 deletions.
1 change: 1 addition & 0 deletions packages/interface/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"lowdb": "^1.0.0",
"lucide-react": "^0.316.0",
"maci-cli": "^2.2.0",
"maci-crypto": "^2.2.0",
"maci-domainobjs": "^2.2.0",
"next": "^14.1.0",
"next-auth": "^4.24.5",
Expand Down
6 changes: 4 additions & 2 deletions packages/interface/src/contexts/Maci.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { isAfter } from "date-fns";
import { type Signer, BrowserProvider } from "ethers";
import {
isRegisteredUser,
publishBatch,
type TallyData,
type IGetPollData,
getPoll,
Expand All @@ -26,6 +25,7 @@ import { getSemaphoreProof } from "~/utils/semaphore";
import type { IVoteArgs, MaciContextType, MaciProviderProps } from "./types";
import type { Attestation } from "~/utils/types";
import signUp from "~/utils/signUp";
import { publishBatch } from "~/utils/publishBatch";

export const MaciContext = createContext<MaciContextType | undefined>(undefined);

Expand Down Expand Up @@ -252,7 +252,7 @@ export const MaciProvider: React.FC<MaciProviderProps> = ({ children }: MaciProv
// function to be used to vote on a poll
const onVote = useCallback(
async (votes: IVoteArgs[], onError: () => Promise<void>, onSuccess: () => Promise<void>) => {
if (!signer || !stateIndex || !pollData) {
if (!signer || !smartAccount || !smartAccountClient || !stateIndex || !pollData) {
return;
}

Expand All @@ -279,6 +279,8 @@ export const MaciProvider: React.FC<MaciProviderProps> = ({ children }: MaciProv
privateKey: maciPrivKey!,
pollId: BigInt(pollData.id),
signer,
smartAccount,
smartAccountClient
})
.then(() => onSuccess())
.catch((err: Error) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/interface/src/layouts/BaseLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export const BaseLayout = ({
const { isRegistered } = useMaci();

const manageDisplay = useCallback(() => {
if ((requireAuth && !address) || (requireRegistration && !isRegistered)) {
if (requireRegistration && !isRegistered) {
router.push("/");
}
}, [requireAuth, address, requireRegistration, isRegistered, router]);
Expand Down
7 changes: 0 additions & 7 deletions packages/interface/src/pages/ballot/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,6 @@ const BallotPage = (): JSX.Element => {
const { ballot, sumBallot } = useBallot();
const router = useRouter();
const appState = useAppState();

useEffect(() => {
if (!address) {
router.push("/");
}
}, [address, router]);

const handleSubmit = useCallback(() => {
sumBallot();
}, [sumBallot]);
Expand Down
174 changes: 174 additions & 0 deletions packages/interface/src/utils/publishBatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import {
IPublishBatchArgs,
IPublishBatchData,
PubKey,
MACI__factory as MACIFactory,
Poll__factory as PollFactory,
} from "maci-cli/sdk";
import {
IG1ContractParams,
IMessageContractParams,
Keypair,
PCommand,
PrivKey,
} from "maci-domainobjs";
import { genRandomSalt } from "maci-crypto";
import { publicClient } from "./permissionless";
import { SmartAccountClient } from "permissionless";
import { EntryPoint } from "permissionless/types";
import { KernelEcdsaSmartAccount } from "permissionless/accounts";
import { Address, HttpTransport, Chain } from "viem";
// import { banner, validateSalt } from "node_modules/maci-cli/build/ts/utils"; // TODO: (merge-ok) this import breaks the frontend

const MESSAGE_TREE_ARITY = 5;

type ISmartAccountPublishBatchArgs = IPublishBatchArgs & {
smartAccount: KernelEcdsaSmartAccount<EntryPoint, HttpTransport, Chain>;
smartAccountClient: SmartAccountClient<EntryPoint, HttpTransport, Chain>;
};


/**
* @notice copied from maci-cli/sdk to add sponsorship
* Batch publish new messages to a MACI Poll contract
* @param {IPublishBatchArgs} args - The arguments for the publish command
* @returns {IPublishBatchData} The ephemeral private key used to encrypt the message, transaction hash
*/
export const publishBatch = async ({
messages,
pollId,
maciAddress,
publicKey,
privateKey,
signer,
quiet = true,
smartAccount,
smartAccountClient,
}: ISmartAccountPublishBatchArgs): Promise<IPublishBatchData> => {
// TODO: (merge-ok) should this be added back?
// banner(quiet);

if (!PubKey.isValidSerializedPubKey(publicKey)) {
throw new Error("invalid MACI public key");
}

if (!PrivKey.isValidSerializedPrivKey(privateKey)) {
throw new Error("invalid MACI private key");
}

if (pollId < 0n) {
throw new Error(`invalid poll id ${pollId}`);
}

const userMaciPubKey = PubKey.deserialize(publicKey);
const userMaciPrivKey = PrivKey.deserialize(privateKey);
const maciContract = MACIFactory.connect(maciAddress, signer);
const pollContracts = await maciContract.getPoll(pollId);

const pollContract = PollFactory.connect(pollContracts.poll, signer);

const [treeDepths, coordinatorPubKeyResult] = await Promise.all([
pollContract.treeDepths(),
pollContract.coordinatorPubKey(),
]);
const maxVoteOptions = Number(
BigInt(MESSAGE_TREE_ARITY) ** treeDepths.voteOptionTreeDepth
);

// validate the vote options index against the max leaf index on-chain
messages.forEach(({ stateIndex, voteOptionIndex, salt, nonce }) => {
if (voteOptionIndex < 0 || maxVoteOptions < voteOptionIndex) {
throw new Error("invalid vote option index");
}

// check < 1 cause index zero is a blank state leaf
if (stateIndex < 1) {
throw new Error("invalid state index");
}

if (nonce < 0) {
throw new Error("invalid nonce");
}

// TODO: (merge-ok) add validation back here
// if (salt && !validateSalt(salt)) {
// throw new Error("invalid salt");
// }
});

const coordinatorPubKey = new PubKey([
BigInt(coordinatorPubKeyResult.x.toString()),
BigInt(coordinatorPubKeyResult.y.toString()),
]);

const encryptionKeypair = new Keypair();
const sharedKey = Keypair.genEcdhSharedKey(
encryptionKeypair.privKey,
coordinatorPubKey
);

const payload: [IMessageContractParams, IG1ContractParams][] = messages.map(
({ salt, stateIndex, voteOptionIndex, newVoteWeight, nonce }) => {
const userSalt = salt ? BigInt(salt) : genRandomSalt();

// create the command object
const command = new PCommand(
stateIndex,
userMaciPubKey,
voteOptionIndex,
newVoteWeight,
nonce,
BigInt(pollId),
userSalt
);

// sign the command with the user private key
const signature = command.sign(userMaciPrivKey);

const message = command.encrypt(signature, sharedKey);

return [
message.asContractParam(),
encryptionKeypair.pubKey.asContractParam(),
];
}
);

const preparedMessages = payload.map(([message]) => message);
const preparedKeys = payload.map(([, key]) => key);

// TODO: (merge-ok) make this type casting/handling nicer
const reversedMessages = preparedMessages.reverse().map((item) => ({
data: item.data.map((val) => BigInt(val)) as [
bigint,
bigint,
bigint,
bigint,
bigint,
bigint,
bigint,
bigint,
bigint,
bigint,
],
}));
const reversedKeys = preparedKeys.reverse() as readonly {
x: bigint;
y: bigint;
}[];

const { request } = await publicClient.simulateContract({
account: smartAccount,
address: pollContracts.poll as Address,
abi: PollFactory.abi,
functionName: "publishMessageBatch",
args: [reversedMessages, reversedKeys],
});
const txHash = await smartAccountClient.writeContract(request);

return {
hash: txHash,
encryptedMessages: preparedMessages,
privateKey: encryptionKeypair.privKey.serialize(),
};
};
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 5928b77

Please sign in to comment.