Skip to content

Commit 0af3157

Browse files
authored
Validate that n of m elections are encrypting and decrypting correctly (#315)
* Validate that n of m elections are encrypting and decrypting correctly * Extract styleid * Fix problem with inconsistent slashes on windows build targets * Consistently use forward slashes on windows * Fix slashes for all build targets * Fix slashes one level higher * Fix cpm source cache as well
1 parent 370fdff commit 0af3157

File tree

4 files changed

+110
-58
lines changed

4 files changed

+110
-58
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ ELECTIONGUARD_BINDING_DIR=$(realpath .)/bindings
55
ELECTIONGUARD_BINDING_LIB_DIR=$(ELECTIONGUARD_BINDING_DIR)/netstandard/ElectionGuard/ElectionGuard.Encryption
66
ELECTIONGUARD_BINDING_BENCH_DIR=$(ELECTIONGUARD_BINDING_DIR)/netstandard/ElectionGuard/ElectionGuard.Encryption.Bench
77
ELECTIONGUARD_BINDING_TEST_DIR=$(ELECTIONGUARD_BINDING_DIR)/netstandard/ElectionGuard/ElectionGuard.Encryption.Tests
8-
ELECTIONGUARD_BUILD_DIR=$(realpath .)/build
8+
ELECTIONGUARD_BUILD_DIR=$(subst \,/,$(realpath .))/build
99
ELECTIONGUARD_BUILD_DIR_WIN=$(subst \c\,C:\,$(subst /,\,$(ELECTIONGUARD_BUILD_DIR)))
1010
ELECTIONGUARD_BUILD_APPS_DIR=$(ELECTIONGUARD_BUILD_DIR)/apps
1111
ELECTIONGUARD_BUILD_BINDING_DIR=$(ELECTIONGUARD_BUILD_DIR)/bindings
1212
ELECTIONGUARD_BUILD_LIBS_DIR=$(ELECTIONGUARD_BUILD_DIR)/libs
13-
CPM_SOURCE_CACHE=$(realpath .)/.cache/CPM
13+
CPM_SOURCE_CACHE=$(subst \,/,$(realpath .))/.cache/CPM
1414

1515
# Detect operating system
1616
ifeq ($(OS),Windows_NT)

bindings/netstandard/ElectionGuard/ElectionGuard.Encryption.Tests/TestEncrypt.cs

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using ElectionGuard.Encryption.Utils;
33
using NUnit.Framework;
44

5-
namespace ElectionGuard.Encrypt.Tests
5+
namespace ElectionGuard.Encryption.Tests
66
{
77
[TestFixture]
88
public class TestEncrypt
@@ -88,7 +88,7 @@ public void Test_Encrypt_Ballot_Undervote_Succeeds()
8888
var mediator = new EncryptionMediator(internalManifest, context, device);
8989

9090
// Act
91-
var ballot = BallotGenerator.GetFakeBallot(internalManifest, 1);
91+
var ballot = BallotGenerator.GetFakeBallot(internalManifest);
9292
var ciphertext = mediator.Encrypt(ballot);
9393

9494
// Assert
@@ -116,6 +116,40 @@ public void Test_Encrypt_Ballot_Overvote_Succeeds()
116116
Assert.That(ciphertext.IsValidEncryption(context.ManifestHash, keypair.PublicKey, context.CryptoExtendedBaseHash));
117117
}
118118

119+
[Test]
120+
public void Test_EncryptAndDecryptOfBallot_WithMultipleVotesAllowed()
121+
{
122+
// Arrange
123+
var keypair = ElGamalKeyPair.FromSecret(Constants.TWO_MOD_Q);
124+
var manifest = ManifestGenerator.GetManifestFromFile();
125+
var internalManifest = new InternalManifest(manifest);
126+
var context = new CiphertextElectionContext(
127+
1UL, 1UL, keypair.PublicKey, Constants.TWO_MOD_Q, internalManifest.ManifestHash);
128+
var device = new EncryptionDevice(12345UL, 23456UL, 34567UL, "Location");
129+
var mediator = new EncryptionMediator(internalManifest, context, device);
130+
const string styleId = "congress-district-7-arlington-pismo-beach";
131+
var ballot = BallotGenerator.GetFakeBallotWithContest(internalManifest, "pismo-beach-school-board-contest", 3, styleId);
132+
133+
// Act
134+
var ciphertext = mediator.Encrypt(ballot);
135+
136+
// Assert
137+
Assert.AreEqual(5, ciphertext.ContestsSize);
138+
var pismoBeach = FindContestOrDefault(ciphertext, i => i.ObjectId == "pismo-beach-school-board-contest");
139+
Assert.IsNotNull(pismoBeach);
140+
Assert.AreEqual(1L, DecryptSelection(pismoBeach, keypair, 0));
141+
Assert.AreEqual(1L, DecryptSelection(pismoBeach, keypair, 1));
142+
Assert.AreEqual(1L, DecryptSelection(pismoBeach, keypair, 2));
143+
Assert.AreEqual(0L, DecryptSelection(pismoBeach, keypair, 3));
144+
Assert.AreEqual(0L, DecryptSelection(pismoBeach, keypair, 4));
145+
}
146+
147+
private static ulong? DecryptSelection(CiphertextBallotContest pismoBeach, ElGamalKeyPair keypair, int index)
148+
{
149+
var selection = pismoBeach.GetSelectionAt((ulong)index);
150+
return selection.Ciphertext.Decrypt(keypair.SecretKey);
151+
}
152+
119153
[Test]
120154
public void Test_Compact_Encrypt_Ballot_Simple_Succeeds()
121155
{
@@ -168,7 +202,7 @@ public void Test_EncryptMediator_Hashes_Match()
168202
var mediator = new EncryptionMediator(internalManifest, context, device);
169203
Assert.IsNotNull(mediator); // should not be null if it gets created
170204
}
171-
catch (Exception ex)
205+
catch (Exception)
172206
{
173207
// if there is an exception then the manifest hash would not be equal
174208
Assert.AreNotEqual(context.ManifestHash.ToHex(), internalManifest.ManifestHash.ToHex());
@@ -189,13 +223,27 @@ public void Test_EncryptMediator_Hashes_Dont_Match()
189223
var mediator = new EncryptionMediator(internalManifest, context, device);
190224
Assert.IsNull(mediator); // should not be created, so null at best
191225
}
192-
catch (Exception ex)
226+
catch (Exception)
193227
{
194228
// if there is an exception then the manifest hash would not be equal
195229
Assert.AreNotEqual(context.ManifestHash.ToHex(), internalManifest.ManifestHash.ToHex());
196230
}
197231
}
198232

199233

234+
private static CiphertextBallotContest FindContestOrDefault(CiphertextBallot ballot, Func<CiphertextBallotContest, bool> func)
235+
{
236+
var contestSize = (int)ballot.ContestsSize;
237+
for (int i = 0; i < contestSize; i++)
238+
{
239+
var contest = ballot.GetContestAt((ulong)i);
240+
if (func(contest))
241+
{
242+
return contest;
243+
}
244+
}
245+
246+
return null;
247+
}
200248
}
201249
}

bindings/netstandard/ElectionGuard/ElectionGuard.Encryption.Utils/Generators/Ballot.cs

Lines changed: 0 additions & 52 deletions
This file was deleted.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
5+
namespace ElectionGuard.Encryption.Utils
6+
{
7+
public class BallotGenerator
8+
{
9+
public static PlaintextBallotSelection SelectionFrom(SelectionDescription description, bool isAffirmative = false, bool isPlaceholder = false)
10+
{
11+
return new PlaintextBallotSelection(description.ObjectId, isAffirmative ? 1UL : 0UL, isPlaceholder);
12+
}
13+
14+
public static PlaintextBallotContest ContestFrom(ContestDescription contest, Func<string, int, bool> voteResultFunc)
15+
{
16+
List<PlaintextBallotSelection> selections = new List<PlaintextBallotSelection>();
17+
18+
for (ulong i = 0; i < contest.SelectionsSize; i++)
19+
{
20+
var isAffirmative = voteResultFunc(contest.ObjectId, (int)i);
21+
selections.Add(SelectionFrom(contest.GetSelectionAtIndex(i), isAffirmative));
22+
}
23+
24+
return new PlaintextBallotContest(contest.ObjectId, selections.ToArray());
25+
}
26+
27+
public static PlaintextBallot GetFakeBallotWithContest(InternalManifest manifest, string contestId, int numberOfSelections, string styleId)
28+
{
29+
return GetFakeBallotInternal(manifest, styleId, (currentContestId, selectionId) =>
30+
{
31+
if (currentContestId == contestId)
32+
return selectionId < numberOfSelections;
33+
return selectionId == 0;
34+
});
35+
}
36+
37+
public static PlaintextBallot GetFakeBallot(InternalManifest manifest, uint maxChoices = 1)
38+
{
39+
var styleId = manifest.GetBallotStyleAtIndex(0).ObjectId;
40+
return GetFakeBallotInternal(manifest, styleId, (_, selectionId) => selectionId < maxChoices);
41+
}
42+
43+
public static PlaintextBallot GetFakeBallotInternal(InternalManifest manifest, string styleId,
44+
Func<string, int, bool> voteResultFunc)
45+
{
46+
List<PlaintextBallotContest> contests = new List<PlaintextBallotContest>();
47+
for (ulong i = 0; i < manifest.ContestsSize; i++)
48+
{
49+
contests.Add(ContestFrom(manifest.GetContestAtIndex(i), voteResultFunc));
50+
}
51+
52+
const string ballotId = "ballot-id-123";
53+
return new PlaintextBallot(ballotId, styleId, contests.ToArray());
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)