From 2ff3fb19accb8e8c850505241802938f8143153d Mon Sep 17 00:00:00 2001 From: cmsigrist <capucine.berger@epfl.ch> Date: Sun, 19 Jun 2022 12:03:56 +0200 Subject: [PATCH 1/2] Fix ballot encoding, encode the ID of the questions --- web/frontend/src/mocks/handlers.ts | 6 +- web/frontend/src/mocks/mockData.ts | 56 +++++++++++++++++++ web/frontend/src/mocks/setupMockElections.ts | 19 +++++++ web/frontend/src/pages/ballot/Show.tsx | 2 +- .../pages/ballot/components/VoteEncode.tsx | 6 +- web/frontend/src/pages/election/Result.tsx | 56 ++++++++++++------- 6 files changed, 118 insertions(+), 27 deletions(-) diff --git a/web/frontend/src/mocks/handlers.ts b/web/frontend/src/mocks/handlers.ts index 599ce5374..49c9112d6 100644 --- a/web/frontend/src/mocks/handlers.ts +++ b/web/frontend/src/mocks/handlers.ts @@ -41,7 +41,7 @@ const CHANGE_STATUS_TIMER = 2000; const INIT_TIMER = 1000; const SETUP_TIMER = 2000; const SHUFFLE_TIMER = 2000; -const DECRYPT_TIMER = 3000; +const DECRYPT_TIMER = 1000; const defaultProxy = 'http://localhost/'; @@ -140,8 +140,8 @@ export const handlers = [ Result: [], Roster: mockRoster, Configuration: configuration, - BallotSize: 290, - ChunksPerBallot: 10, + BallotSize: 291, + ChunksPerBallot: 11, }); return newElectionID; diff --git a/web/frontend/src/mocks/mockData.ts b/web/frontend/src/mocks/mockData.ts index 06d18701b..98be52a83 100644 --- a/web/frontend/src/mocks/mockData.ts +++ b/web/frontend/src/mocks/mockData.ts @@ -175,6 +175,31 @@ const mockElection2: any = { ], }; +const mockElection3: any = { + MainTitle: 'Lunch', + Scaffold: [ + { + ID: '3cVHIxpx', + Title: 'Choose your lunch', + Order: ['PGPhhJlJ'], + Ranks: [], + Selects: [], + Texts: [ + { + ID: 'PGPhhJlJ', + Title: 'Select what you want', + MaxN: 4, + MinN: 0, + MaxLength: 50, + Regex: '', + Choices: ['Firstname', 'Main 🍕', 'Drink 🧃', 'Dessert 🍰'], + }, + ], + Subjects: [], + }, + ], +}; + const mockElectionResult21: Results = { SelectResultIDs: [(0x3fb2).toString(), (0xa319).toString()], SelectResult: [ @@ -220,6 +245,33 @@ const mockElectionResult23: Results = { TextResult: [['Another Name', 'Jane Doe']], }; +const mockElectionResult31: Results = { + SelectResultIDs: [], + SelectResult: [], + RankResultIDs: [], + RankResult: [], + TextResultIDs: ['PGPhhJlJ'], + TextResult: [['Alice', 'Pizza', 'Ice cold water', '🍒🍒🍒🍒']], +}; + +const mockElectionResult32: Results = { + SelectResultIDs: [], + SelectResult: [], + RankResultIDs: [], + RankResult: [], + TextResultIDs: ['PGPhhJlJ'], + TextResult: [['Bob', 'Pizza', 'Coke', '🍒🍒🍒']], +}; + +const mockElectionResult33: Results = { + SelectResultIDs: null, + SelectResult: null, + RankResultIDs: null, + RankResult: null, + TextResultIDs: null, + TextResult: null, +}; + export { mockElection1, mockElectionResult11, @@ -227,7 +279,11 @@ export { mockElection2, mockElectionResult21, mockElectionResult22, + mockElection3, mockElectionResult23, + mockElectionResult31, + mockElectionResult32, + mockElectionResult33, mockRoster, mockNodes, }; diff --git a/web/frontend/src/mocks/setupMockElections.ts b/web/frontend/src/mocks/setupMockElections.ts index cd2ac0ed9..43986e4bf 100644 --- a/web/frontend/src/mocks/setupMockElections.ts +++ b/web/frontend/src/mocks/setupMockElections.ts @@ -5,11 +5,15 @@ import { NodeStatus } from 'types/node'; import { mockElection1, mockElection2, + mockElection3, mockElectionResult11, mockElectionResult12, mockElectionResult21, mockElectionResult22, mockElectionResult23, + mockElectionResult31, + mockElectionResult32, + mockElectionResult33, mockNodes, mockRoster, } from './mockData'; @@ -39,6 +43,7 @@ const setupMockElection = () => { const electionID1 = '36kSJ0tH'; const electionID2 = 'Bnq9gLmf'; + const electionID3 = 'Afdv4ffl'; mockElections.set(electionID1, { ElectionID: electionID1, @@ -69,6 +74,20 @@ const setupMockElection = () => { mockResults.set(electionID2, [mockElectionResult21, mockElectionResult22, mockElectionResult23]); mockDKG.set(electionID2, mockDKGSetup); + mockElections.set(electionID3, { + ElectionID: electionID3, + Status: Status.Open, + Pubkey: 'XL4V6EMIICW', + Result: [], + Roster: mockRoster, + Configuration: unmarshalConfig(mockElection3), + BallotSize: 291, + ChunksPerBallot: 11, + }); + + mockResults.set(electionID3, [mockElectionResult31, mockElectionResult32, mockElectionResult33]); + mockDKG.set(electionID3, mockDKGSetup); + return { mockElections, mockResults, mockDKG, mockNodeProxyAddresses }; }; diff --git a/web/frontend/src/pages/ballot/Show.tsx b/web/frontend/src/pages/ballot/Show.tsx index db5ddcc44..8753444d2 100644 --- a/web/frontend/src/pages/ballot/Show.tsx +++ b/web/frontend/src/pages/ballot/Show.tsx @@ -27,7 +27,7 @@ const Ballot: FC = () => { const { electionId } = useParams(); const UserID = sessionStorage.getItem('id'); - const { loading, configObj, electionID, status, pubKey, ballotSize, chunksPerBallot } = + const { loading, configObj, electionID, status, pubKey, chunksPerBallot } = useElection(electionId); const { configuration, answers, setAnswers } = useConfiguration(configObj); diff --git a/web/frontend/src/pages/ballot/components/VoteEncode.tsx b/web/frontend/src/pages/ballot/components/VoteEncode.tsx index b10ac7594..05f635bd9 100644 --- a/web/frontend/src/pages/ballot/components/VoteEncode.tsx +++ b/web/frontend/src/pages/ballot/components/VoteEncode.tsx @@ -7,14 +7,14 @@ export function voteEncode(answers: Answers, chunksPerBallot: number): string[] let encodedBallot = ''; answers.SelectAnswers.forEach((selectAnswer, id) => { - encodedBallot += SELECT + ':' + id + ':'; + encodedBallot += SELECT + ':' + Buffer.from(id).toString('base64') + ':'; selectAnswer.forEach((answer) => (encodedBallot += answer ? '1,' : '0,')); encodedBallot = encodedBallot.slice(0, -1); encodedBallot += '\n'; }); answers.RankAnswers.forEach((rankAnswer, id) => { - encodedBallot += RANK + ':' + id + ':'; + encodedBallot += RANK + ':' + Buffer.from(id).toString('base64') + ':'; const position = Array<number>(rankAnswer.length); for (let i = 0; i < rankAnswer.length; i++) { position[rankAnswer[i]] = i; @@ -25,7 +25,7 @@ export function voteEncode(answers: Answers, chunksPerBallot: number): string[] }); answers.TextAnswers.forEach((textAnswer, id) => { - encodedBallot += TEXT + ':' + id + ':'; + encodedBallot += TEXT + ':' + Buffer.from(id).toString('base64') + ':'; // each answer is first transformed into bytes then encoded in base64 textAnswer.forEach((answer) => (encodedBallot += Buffer.from(answer).toString('base64') + ',')); encodedBallot = encodedBallot.slice(0, -1); diff --git a/web/frontend/src/pages/election/Result.tsx b/web/frontend/src/pages/election/Result.tsx index f63b9f5db..adeb60b5d 100644 --- a/web/frontend/src/pages/election/Result.tsx +++ b/web/frontend/src/pages/election/Result.tsx @@ -75,9 +75,15 @@ const ElectionResult: FC = () => { let textRes: TextResults = new Map<ID, string[][]>(); result.forEach((res) => { - groupByID(selectRes, res.SelectResultIDs, res.SelectResult, true); - groupByID(rankRes, res.RankResultIDs, res.RankResult); - groupByID(textRes, res.TextResultIDs, res.TextResult); + if ( + res.SelectResultIDs !== null && + res.RankResultIDs !== null && + res.TextResultIDs !== null + ) { + groupByID(selectRes, res.SelectResultIDs, res.SelectResult, true); + groupByID(rankRes, res.RankResultIDs, res.RankResult); + groupByID(textRes, res.TextResultIDs, res.TextResult); + } }); return { rankRes, selectRes, textRes }; @@ -104,20 +110,26 @@ const ElectionResult: FC = () => { switch (element.Type) { case RANK: const rank = element as RankQuestion; - res = countRankResult(rankResult.get(id), element as RankQuestion).resultsInPercent.map( - (percent, index) => { - return { Candidate: rank.Choices[index], Percentage: `${percent}%` }; - } - ); - dataToDownload.push({ Title: element.Title, Results: res }); + + if (rankResult.has(id)) { + res = countRankResult(rankResult.get(id), element as RankQuestion).resultsInPercent.map( + (percent, index) => { + return { Candidate: rank.Choices[index], Percentage: `${percent}%` }; + } + ); + dataToDownload.push({ Title: element.Title, Results: res }); + } break; case SELECT: const select = element as SelectQuestion; - res = countSelectResult(selectResult.get(id)).resultsInPercent.map((percent, index) => { - return { Candidate: select.Choices[index], Percentage: `${percent}%` }; - }); - dataToDownload.push({ Title: element.Title, Results: res }); + + if (selectResult.has(id)) { + res = countSelectResult(selectResult.get(id)).resultsInPercent.map((percent, index) => { + return { Candidate: select.Choices[index], Percentage: `${percent}%` }; + }); + dataToDownload.push({ Title: element.Title, Results: res }); + } break; case SUBJECT: @@ -125,10 +137,12 @@ const ElectionResult: FC = () => { break; case TEXT: - res = Array.from(countTextResult(textResult.get(id)).resultsInPercent).map((r) => { - return { Candidate: r[0], Percentage: `${r[1]}%` }; - }); - dataToDownload.push({ Title: element.Title, Results: res }); + if (textResult.has(id)) { + res = Array.from(countTextResult(textResult.get(id)).resultsInPercent).map((r) => { + return { Candidate: r[0], Percentage: `${r[1]}%` }; + }); + dataToDownload.push({ Title: element.Title, Results: res }); + } break; } }); @@ -160,16 +174,18 @@ const ElectionResult: FC = () => { return ( <div className="pl-4 pb-4 sm:pl-6 sm:pb-6"> <h2 className="text-lg pb-2">{element.Title}</h2> - {element.Type === RANK && ( + {element.Type === RANK && rankResult.has(element.ID) && ( <RankResult rank={element as RankQuestion} rankResult={rankResult.get(element.ID)} /> )} - {element.Type === SELECT && ( + {element.Type === SELECT && selectResult.has(element.ID) && ( <SelectResult select={element as SelectQuestion} selectResult={selectResult.get(element.ID)} /> )} - {element.Type === TEXT && <TextResult textResult={textResult.get(element.ID)} />} + {element.Type === TEXT && textResult.has(element.ID) && ( + <TextResult textResult={textResult.get(element.ID)} /> + )} </div> ); }; From cb8fedca17dfff5d7b7fccee0a71c6e47c72846a Mon Sep 17 00:00:00 2001 From: cmsigrist <capucine.berger@epfl.ch> Date: Sun, 19 Jun 2022 13:51:11 +0200 Subject: [PATCH 2/2] Reduce the size of the unique id to 3B for questions element --- docs/ballot_encoding.md | 2 +- web/frontend/src/mocks/mockData.ts | 8 ++++---- web/frontend/src/types/getObjectType.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/ballot_encoding.md b/docs/ballot_encoding.md index 4f73d2f5e..01c89ae4d 100644 --- a/docs/ballot_encoding.md +++ b/docs/ballot_encoding.md @@ -13,7 +13,7 @@ The answers to questions are encoded in the following way, with one question per TYPE = "select"|"text"|"rank" SEP = ":" -ID = up to 3 bytes, encoded in base64 +ID = 3 bytes, encoded in base64 ANSWERS = <answer>[","<answer>]* ANSWER = <select_answer>|<text_answer>|<rank_answer> SELECT_ANSWER = "0"|"1" diff --git a/web/frontend/src/mocks/mockData.ts b/web/frontend/src/mocks/mockData.ts index 98be52a83..f4634f8ca 100644 --- a/web/frontend/src/mocks/mockData.ts +++ b/web/frontend/src/mocks/mockData.ts @@ -181,12 +181,12 @@ const mockElection3: any = { { ID: '3cVHIxpx', Title: 'Choose your lunch', - Order: ['PGPhhJlJ'], + Order: ['PGP'], Ranks: [], Selects: [], Texts: [ { - ID: 'PGPhhJlJ', + ID: 'PGP', Title: 'Select what you want', MaxN: 4, MinN: 0, @@ -250,7 +250,7 @@ const mockElectionResult31: Results = { SelectResult: [], RankResultIDs: [], RankResult: [], - TextResultIDs: ['PGPhhJlJ'], + TextResultIDs: ['PGP'], TextResult: [['Alice', 'Pizza', 'Ice cold water', '🍒🍒🍒🍒']], }; @@ -259,7 +259,7 @@ const mockElectionResult32: Results = { SelectResult: [], RankResultIDs: [], RankResult: [], - TextResultIDs: ['PGPhhJlJ'], + TextResultIDs: ['PGP'], TextResult: [['Bob', 'Pizza', 'Coke', '🍒🍒🍒']], }; diff --git a/web/frontend/src/types/getObjectType.ts b/web/frontend/src/types/getObjectType.ts index f5703326d..055b34857 100644 --- a/web/frontend/src/types/getObjectType.ts +++ b/web/frontend/src/types/getObjectType.ts @@ -2,7 +2,7 @@ import ShortUniqueId from 'short-unique-id'; import * as types from './configuration'; import { ID, RANK, SELECT, SUBJECT, TEXT } from './configuration'; -const uid: Function = new ShortUniqueId({ length: 8 }); +const uid: Function = new ShortUniqueId({ length: 3 }); const emptyConfiguration = (): types.Configuration => { return { MainTitle: '', Scaffold: [] };