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: [] };