Skip to content

Commit

Permalink
Fixed anonymous voteHash by using the votePackage to generate zk-…
Browse files Browse the repository at this point in the history
…inputs
  • Loading branch information
marcvelmer committed Feb 29, 2024
1 parent 047391b commit 331c2f8
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 50 deletions.
28 changes: 16 additions & 12 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,13 +275,15 @@ export class VocdoniSDKClient {
* @param election
* @param wallet
* @param signature
* @param votePackage
* @param password
* @returns {Promise<ZkProof>}
*/
private async calcZKProofForWallet(
election: PublishedElection,
wallet: Wallet | Signer,
signature: string,
votePackage: Buffer,
password: string = '0'
): Promise<ZkProof> {
const [address, censusProof] = await Promise.all([
Expand All @@ -304,7 +306,8 @@ export class VocdoniSDKClient {
zkProof.censusRoot,
zkProof.censusSiblings,
censusProof.root,
censusProof.siblings
censusProof.siblings,
votePackage
)
)
.then((circuits) => this.anonymousService.generateZkProof(circuits));
Expand Down Expand Up @@ -835,6 +838,14 @@ export class VocdoniSDKClient {
electionId: election.id,
};

let processKeys = null;
if (election?.electionType.secretUntilTheEnd) {
processKeys = await this.electionService.keys(election.id).then((encryptionKeys) => ({
encryptionPubKeys: encryptionKeys.publicKeys,
}));
}
const { votePackage } = VoteCore.packageVoteContent(vote.votes, processKeys);

let censusProof: CspCensusProof | CensusProof | ZkProof;
if (election.census.type == CensusType.WEIGHTED) {
censusProof = await this.fetchProofForWallet(election.census.censusId, this.wallet);
Expand All @@ -853,9 +864,9 @@ export class VocdoniSDKClient {
signature,
};
if (vote instanceof AnonymousVote) {
censusProof = await this.calcZKProofForWallet(election, this.wallet, signature, vote.password);
censusProof = await this.calcZKProofForWallet(election, this.wallet, signature, votePackage, vote.password);
} else {
censusProof = await this.calcZKProofForWallet(election, this.wallet, signature);
censusProof = await this.calcZKProofForWallet(election, this.wallet, signature, votePackage);
}
yield {
key: VoteSteps.CALC_ZK_PROOF,
Expand All @@ -874,15 +885,8 @@ export class VocdoniSDKClient {
}

let voteTx: { tx: Uint8Array; message: string };
if (election?.electionType.secretUntilTheEnd) {
voteTx = await this.electionService.keys(election.id).then((encryptionKeys) =>
VoteCore.generateVoteTransaction(election, censusProof, vote, {
encryptionPubKeys: encryptionKeys.publicKeys,
})
);
} else {
voteTx = VoteCore.generateVoteTransaction(election, censusProof, vote);
}

voteTx = VoteCore.generateVoteTransaction(election, censusProof, vote, processKeys, votePackage);
yield {
key: VoteSteps.GENERATE_TX,
};
Expand Down
46 changes: 10 additions & 36 deletions src/core/vote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,15 @@ export abstract class VoteCore extends TransactionCore {
public static generateVoteTransaction(
election: PublishedElection,
censusProof: CensusProof | CspCensusProof | ZkProof,
votePackage: Vote,
processKeys?: ProcessKeys
vote: Vote,
processKeys?: ProcessKeys,
votePackage?: Buffer
): { tx: Uint8Array; message: string } {
const message = TxMessage.VOTE.replace('{processId}', strip0x(election.id));
const txData = this.prepareVoteData(election, censusProof, votePackage, processKeys);
const vote = VoteEnvelope.fromPartial(txData);
const txData = this.prepareVoteData(election, censusProof, vote, processKeys, votePackage);
const voteEnvelope = VoteEnvelope.fromPartial(txData);
const tx = Tx.encode({
payload: { $case: 'vote', vote },
payload: { $case: 'vote', vote: voteEnvelope },
}).finish();

return { tx, message };
Expand All @@ -59,18 +60,9 @@ export abstract class VoteCore extends TransactionCore {
election: PublishedElection,
censusProof: CensusProof | CspCensusProof | ZkProof,
vote: Vote,
processKeys?: ProcessKeys
processKeys: ProcessKeys,
generatedVotePackage: Buffer
): object {
// if (!params) throw new Error("Invalid parameters")
// else if (!Array.isArray(params.votes)) throw new Error("Invalid votes array")
// else if (typeof params.processId != "string" || !params.processId.match(/^(0x)?[0-9a-zA-Z]+$/)) throw new Error("Invalid processId")
// else if (params.processKeys) {
// if (!Array.isArray(params.processKeys.encryptionPubKeys) || !params.processKeys.encryptionPubKeys.every(
// item => item && typeof item.idx == "number" && typeof item.key == "string" && item.key.match(/^(0x)?[0-9a-zA-Z]+$/))) {
// throw new Error("Some encryption public keys are not valid")
// }
// }

try {
const proof = this.packageSignedProof(election.id, election.census.type, censusProof);
// const nonce = hexStringToBuffer(Random.getHex());
Expand All @@ -81,7 +73,7 @@ export abstract class VoteCore extends TransactionCore {
proof,
processId: new Uint8Array(Buffer.from(strip0x(election.id), 'hex')),
nonce: new Uint8Array(nonce),
votePackage: new Uint8Array(votePackage),
votePackage: new Uint8Array(generatedVotePackage ?? votePackage),
encryptionKeyIndexes: keyIndexes || [],
};
} catch (error) {
Expand Down Expand Up @@ -180,25 +172,7 @@ export abstract class VoteCore extends TransactionCore {
return CAbundle.encode(bundle).finish();
}

private static packageVoteContent(votes: VoteValues, processKeys?: ProcessKeys) {
// if (!Array.isArray(votes)) throw new Error('Invalid votes');
// else if (votes.some(vote => typeof vote !== 'number'))
// throw new Error('Votes needs to be an array of numbers');
// else if (processKeys) {
// if (
// !Array.isArray(processKeys.encryptionPubKeys) ||
// !processKeys.encryptionPubKeys.every(
// item =>
// item &&
// typeof item.idx === 'number' &&
// typeof item.key === 'string' &&
// item.key.match(/^(0x)?[0-9a-zA-Z]+$/)
// )
// ) {
// throw new Error('Some encryption public keys are not valid');
// }
// }

public static packageVoteContent(votes: VoteValues, processKeys?: ProcessKeys) {
// produce a 8 byte nonce
const nonce = getHex().substring(2, 18);

Expand Down
7 changes: 5 additions & 2 deletions src/services/anonymous.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,14 @@ export class AnonymousService extends Service implements AnonymousServicePropert
sikRoot: string,
sikSiblings: string[],
censusRoot: string,
censusSiblings: string[]
censusSiblings: string[],
votePackage: Buffer
): Promise<CircuitInputs> {
return Promise.all([
AnonymousService.calcCircuitInputs(signature, password, electionId),
AnonymousService.arbo_utils.toHash(AnonymousService.hex_utils.fromBigInt(BigInt(ensure0x(availableWeight)))),
AnonymousService.arbo_utils.toHash(
AnonymousService.hex_utils.fromBigInt(BigInt(ensure0x(votePackage.toString('hex'))))
),
]).then(([circuitInputs, voteHash]) => ({
electionId: circuitInputs.arboElectionId,
nullifier: circuitInputs.nullifier.toString(),
Expand Down
2 changes: 2 additions & 0 deletions test/integration/election.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ApprovalElection,
BudgetElection,
CensusType,
delay,
Election,
ElectionCreationSteps,
ElectionResultsTypeNames,
Expand Down Expand Up @@ -359,6 +360,7 @@ describe('Election integration tests', () => {
electionIdentifier = electionId;
return waitForElectionReady(client, electionId);
})
.then(() => delay(15000))
.then(() =>
Promise.all(
participants
Expand Down
62 changes: 62 additions & 0 deletions test/integration/zk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { clientParams, setFaucetURL } from './util/client.params';
import {
AnonymousService,
AnonymousVote,
delay,
Election,
ElectionStatus,
PlainCensus,
Expand Down Expand Up @@ -114,6 +115,67 @@ describe('zkSNARK test', () => {
expect(election.census.weight).toEqual(BigInt(3));
});
}, 285000);
it('should create an encrypted anonymous election and vote successfully', async () => {
const census = new PlainCensus();
const voter1 = Wallet.createRandom();
const voter2 = Wallet.createRandom();
// User that votes with account with SIK
census.add((client.wallet as Wallet).address);
// User that votes and has no account
census.add(voter1.address);
// User that votes with account without SIK
census.add(voter2.address);

const election = createElection(census, {
anonymous: true,
secretUntilTheEnd: true,
});

await client.createAccount({
sik: true,
password: 'password123',
});

await client
.createElection(election)
.then((electionId) => {
expect(electionId).toMatch(/^[0-9a-fA-F]{64}$/);
client.setElectionId(electionId);
return client.fetchElection();
})
.then((publishedElection) => {
expect(publishedElection.electionType.anonymous).toBeTruthy();
expect(election.electionType.secretUntilTheEnd).toBeTruthy();
return waitForElectionReady(client, publishedElection.id);
})
.then(async () => {
await delay(15000); // wait for process keys to be ready
await expect(async () => {
await client.submitVote(new Vote([0]));
}).rejects.toThrow();
const vote = new AnonymousVote([0], null, 'password123');
return client.submitVote(vote);
})
.then(() => {
client.wallet = voter1;
const vote = new AnonymousVote([0], null, 'password456');
return client.submitVote(vote);
})
.then(() => {
client.wallet = voter2;
return client.createAccount({ sik: false });
})
.then(() => {
const vote = new Vote([1]);
return client.submitVote(vote);
})
.then(() => client.fetchElection())
.then((election) => {
expect(election.voteCount).toEqual(3);
expect(election.census.size).toEqual(3);
expect(election.census.weight).toEqual(BigInt(3));
});
}, 285000);
it('should create an anonymous election, vote and check if the user has voted successfully', async () => {
const census = new PlainCensus();
census.add((client.wallet as Wallet).address);
Expand Down

0 comments on commit 331c2f8

Please sign in to comment.