Skip to content

Commit 708e8c4

Browse files
Feature/c sharp manifest (#210)
* add manifest interfaces internationalized text, language, etc. * add more c interfaces gp units and ballot style, etc. * add some manifest classes * Add missing C# interface members * Add missing documentation * fix missing free * remove commented out code * address pr feedback * fix missing free
1 parent fe0286a commit 708e8c4

13 files changed

Lines changed: 6764 additions & 257 deletions

File tree

.vscode/c_cpp_properties.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"compilerPath": "/usr/bin/gcc",
1010
"cStandard": "c11",
1111
"cppStandard": "c++17",
12-
"intelliSenseMode": "clang-x64"
12+
"intelliSenseMode": "clang-x64",
13+
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
1314
},
1415
{
1516
"name": "Mac",

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

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,84 @@
22
using System.Diagnostics;
33
using NUnit.Framework;
44

5+
using ElectionGuard.Encryption.Tests.Utils;
6+
57
namespace ElectionGuard.Encrypt.Tests
68
{
79
[TestFixture]
810
public class TestManifest
911
{
1012
[Test]
11-
public void Test_Can_Deserialize_Election_Description()
13+
public void Test_Can_Construct_Internationalized_Text()
1214
{
1315
// Arrange
14-
const string data = @"{""ballot_styles"":[{""geopolitical_unit_ids"":[""some-geopolitical-unit-id""],""object_id"":""some-ballot-style-id""}],""candidates"":[{""object_id"":""some-candidate-id-1""},{""object_id"":""some-candidate-id-2""},{""object_id"":""some-candidate-id-3""}],""contests"":[{""ballot_selections"":[{""candidate_id"":""some-candidate-id-1"",""object_id"":""some-object-id-affirmative"",""sequence_order"":0},{""candidate_id"":""some-candidate-id-2"",""object_id"":""some-object-id-negative"",""sequence_order"":1}],""electoral_district_id"":""some-geopoltical-unit-id"",""name"":""some-referendum-contest-name"",""number_elected"":1,""object_id"":""some-referendum-contest-object-id"",""sequence_order"":0,""vote_variation"":""one_of_m""},{""ballot_selections"":[{""candidate_id"":""some-candidate-id-1"",""object_id"":""some-object-id-candidate-1"",""sequence_order"":0},{""candidate_id"":""some-candidate-id-2"",""object_id"":""some-object-id-candidate-2"",""sequence_order"":1},{""candidate_id"":""some-candidate-id-3"",""object_id"":""some-object-id-candidate-3"",""sequence_order"":2}],""electoral_district_id"":""some-geopoltical-unit-id"",""name"":""some-candidate-contest-name"",""number_elected"":2,""object_id"":""some-candidate-contest-object-id"",""sequence_order"":1,""vote_variation"":""one_of_m""}],""election_scope_id"":""some-scope-id"",""end_date"":""2021-02-04T17:19:34Z"",""geopolitical_units"":[{""name"":""some-gp-unit-name"",""object_id"":""some-geopoltical-unit-id"",""type"":""unknown""}],""parties"":[{""object_id"":""some-party-id-1""},{""object_id"":""some-party-id-2""}],""start_date"":""2021-02-04T17:19:34Z"",""type"":""unknown""}";
16+
var language_1 = new Language("some words", "en");
17+
var language_2 = new Language("algunas palabras", "es");
18+
var languages = new[] { language_1, language_2 };
1519

1620
// Act
17-
var result = new Manifest(data);
21+
var subject = new InternationalizedText(languages);
1822

1923
// Assert
20-
Assert.That(result.ElectionScopeId == "some-scope-id");
24+
var actual = subject.GetTextAt(0);
25+
Assert.That(actual.Value == "some words");
26+
}
27+
28+
[Test]
29+
public void Test_Can_Construct_Ballot_style()
30+
{
31+
var gpUnitIds = new[] { "gp-unit-1", "gp-unit-2" };
2132

33+
var subject = new BallotStyle("some-object-id", gpUnitIds);
2234

35+
var actual = subject.GetGeopoliticalUnitIdAt(0);
36+
Assert.That(actual == "gp-unit-1");
2337
}
2438

2539
[Test]
26-
public void Test_Can_Deserialize_Internal_Election_Description()
40+
public void Test_Can_Construct_InternalManifest_From_Manifest_Minimal()
2741
{
28-
// Arrange
29-
const string data = "{\"ballot_styles\":[{\"geopolitical_unit_ids\":[\"geopolitical-unit-1\"],\"image_uri\":\"some-uri\",\"object_id\":\"some-ballot-style-id\",\"party_ids\":[\"party-1\"]}],\"contests\":[{\"ballot_placeholders\":[{\"candidate_id\":\"candidate-4-id\",\"object_id\":\"contest-1-placeholder-selection-4-id\",\"sequence_order\":4}],\"ballot_selections\":[{\"candidate_id\":\"candidate-1-id\",\"object_id\":\"contest-1-selection-1-id\",\"sequence_order\":1},{\"candidate_id\":\"candidate-2-id\",\"object_id\":\"contest-1-selection-2-id\",\"sequence_order\":2},{\"candidate_id\":\"candidate-3-id\",\"object_id\":\"contest-1-selection-3-id\",\"sequence_order\":3}],\"ballot_subtitle\":{\"text\":[{\"language\":\"en\",\"value\":\"some-title-string\"},{\"language\":\"es\",\"value\":\"some-title-string-es\"}]},\"ballot_title\":{\"text\":[{\"language\":\"en\",\"value\":\"some-title-string\"},{\"language\":\"es\",\"value\":\"some-title-string-es\"}]},\"electoral_district_id\":\"geopolitical-unit-1\",\"name\":\"contest-1-name\",\"number_elected\":2,\"object_id\":\"contest-1-id\",\"sequence_order\":1,\"vote_variation\":\"n_of_m\",\"votes_allowed\":2},{\"ballot_placeholders\":[{\"candidate_id\":\"candidate-3-id\",\"object_id\":\"contest-2-placeholder-selection-3-id\",\"sequence_order\":3}],\"ballot_selections\":[{\"candidate_id\":\"candidate-1-id\",\"object_id\":\"contest-2-selection-1-id\",\"sequence_order\":1},{\"candidate_id\":\"candidate-2-id\",\"object_id\":\"contest-2-selection-2-id\",\"sequence_order\":2}],\"ballot_subtitle\":{\"text\":[{\"language\":\"en\",\"value\":\"some-title-string\"},{\"language\":\"es\",\"value\":\"some-title-string-es\"}]},\"ballot_title\":{\"text\":[{\"language\":\"en\",\"value\":\"some-title-string\"},{\"language\":\"es\",\"value\":\"some-title-string-es\"}]},\"electoral_district_id\":\"geopolitical-unit-1\",\"name\":\"contest-2-name\",\"number_elected\":2,\"object_id\":\"contest-2-id\",\"sequence_order\":2,\"vote_variation\":\"n_of_m\",\"votes_allowed\":2}],\"manifest_hash\":\"02\",\"geopolitical_units\":[{\"contact_information\":{\"address_line\":[\"some-street\",\"some-city\",\"some-whatever\"],\"name\":\"gp-unit-contact-info\"},\"name\":\"district-1-id\",\"object_id\":\"geopolitical-unit-1\",\"type\":\"city\"}]}";
42+
// Get a simple manifest that shows the bare minimum data required
43+
var manifest = ManifestGenerator.GetJeffersonCountyManifest_Minimal();
44+
var internalManifest = new InternalManifest(manifest);
3045

31-
// Act
32-
var result = new InternalManifest(data);
46+
Assert.That(manifest.CryptoHash().ToHex() == internalManifest.ManifestHash.ToHex());
47+
Assert.That(manifest.IsValid());
48+
}
3349

34-
// Assert
35-
Assert.That(result.ManifestHash.ToHex() == "02");
50+
[Test]
51+
public void Test_Can_Construct_InternalManifest_From_Manifest_MultipleBallotStyles()
52+
{
53+
// Get a slightly more complex manifest that shows including multiple ballot styles
54+
var manifest = ManifestGenerator.GetJeffersonCountyManifest_MultipleBallotStyles();
55+
var internalManifest = new InternalManifest(manifest);
56+
57+
Assert.That(manifest.CryptoHash().ToHex() == internalManifest.ManifestHash.ToHex());
58+
Assert.That(manifest.IsValid());
59+
}
60+
61+
[Test]
62+
public void Test_Can_Serialize_Manifest_Minimal()
63+
{
64+
var manifest = ManifestGenerator.GetJeffersonCountyManifest_Minimal();
65+
var json = manifest.ToJson();
66+
67+
var result = new Manifest(json);
68+
69+
Assert.That(manifest.CryptoHash().ToHex() == result.CryptoHash().ToHex());
70+
Assert.That(result.IsValid());
71+
}
72+
73+
[Test]
74+
public void Test_Can_Serialize_Manifest_MultipleBallotStyles()
75+
{
76+
var manifest = ManifestGenerator.GetJeffersonCountyManifest_MultipleBallotStyles();
77+
var json = manifest.ToJson();
78+
79+
var result = new Manifest(json);
80+
81+
Assert.That(manifest.CryptoHash().ToHex() == result.CryptoHash().ToHex());
82+
Assert.That(result.IsValid());
3683
}
3784
}
3885
}

bindings/netstandard/ElectionGuard/ElectionGuard.Encryption.Tests/Utils/Manifest.cs

Lines changed: 193 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,203 @@ namespace ElectionGuard.Encryption.Tests.Utils
99
{
1010
public class ManifestGenerator
1111
{
12-
public static Manifest GetJeffersonCountyManifest()
12+
/// <summary>
13+
/// A test manifest that is loaded from a json file
14+
/// </summary>
15+
public static Manifest GetJeffersonCountyManifest_FromFile()
1316
{
1417
var path = Path.Combine(AppContext.BaseDirectory, "Data", "election_manifest_jefferson_county.json");
1518
var text = File.ReadAllText(path);
1619
return new Manifest(text);
1720
}
21+
22+
/// <summary>
23+
/// A minimal test manifest demonstrating a single contest with two choices and a writein
24+
/// </summary>
25+
public static Manifest GetJeffersonCountyManifest_Minimal()
26+
{
27+
var gpUnits = new List<GeopoliticalUnit>
28+
{
29+
new GeopoliticalUnit("jefferson-county", "Jefferson County", ReportingUnitType.county)
30+
};
31+
32+
var candidates = new List<Candidate>
33+
{
34+
new Candidate("benjamin-franklin", isWriteIn: false),
35+
new Candidate("john-adams", isWriteIn: false),
36+
new Candidate("write-in", isWriteIn: true)
37+
};
38+
39+
var contests = new List<ContestDescription>
40+
{
41+
new ContestDescription(
42+
objectId: "justice-supreme-court",
43+
electoralDistrictId: gpUnits[0].ObjectId,
44+
sequenceOrder: 0,
45+
VoteVariationType.n_of_m,
46+
numberElected: 1,
47+
name: "Justice of the Supreme Court",
48+
selections: new[]
49+
{
50+
new SelectionDescription(
51+
"benjamin-franklin-selection", candidates[0].CandidateId, sequenceOrder: 0),
52+
new SelectionDescription(
53+
"john-adams-selection", candidates[1].CandidateId, sequenceOrder: 1),
54+
new SelectionDescription(
55+
"write-in-selection", candidates[2].CandidateId, sequenceOrder: 2)
56+
})
57+
};
58+
59+
var ballotStyles = new List<BallotStyle>
60+
{
61+
new BallotStyle(
62+
"jefferson-county-ballot-style",
63+
new[] { gpUnits[0].ObjectId })
64+
};
65+
66+
67+
return new Manifest(
68+
"jefferson-county-open-primary",
69+
ElectionType.primary,
70+
startDate: DateTime.UtcNow,
71+
endDate: DateTime.UtcNow.AddDays(1),
72+
gpUnits.ToArray(),
73+
(new List<Party>()).ToArray(),
74+
candidates.ToArray(),
75+
contests.ToArray(),
76+
ballotStyles.ToArray()
77+
);
78+
}
79+
80+
/// <summary>
81+
/// A test manifest demonstrating multiple ballot styles.
82+
/// In the ElectionGuard data structure each contest belongs
83+
/// to a Geopolitical Unit, and then each Geopolitical Unit is part of
84+
/// a ballot style. This example also adds some more metadata
85+
/// such as language transaltions for certain objects
86+
/// </summary>
87+
public static Manifest GetJeffersonCountyManifest_MultipleBallotStyles()
88+
{
89+
var gpUnits = new List<GeopoliticalUnit>
90+
{
91+
new GeopoliticalUnit("jefferson-county", "Jefferson County", ReportingUnitType.county),
92+
new GeopoliticalUnit(
93+
"jefferson-county-school-district-1", "Jefferson County School District 1", ReportingUnitType.school)
94+
};
95+
96+
var parties = new List<Party>
97+
{
98+
new Party("federalist-party"),
99+
new Party("whig-party")
100+
};
101+
102+
var candidates = new List<Candidate>
103+
{
104+
new Candidate("benjamin-franklin", isWriteIn: false),
105+
new Candidate("john-adams", isWriteIn: false),
106+
// The write-in candidate can be reused for all contests
107+
new Candidate("write-in", isWriteIn: true),
108+
new Candidate("referendum-pineapple-affirmative", new InternationalizedText (
109+
new []{
110+
// Adding language info isn't requred for the library to function
111+
// but is useful for providing what data was displayed to users
112+
// since it is included in the manifest hash.
113+
new Language("Pineapple should be banned on pizza", "en"),
114+
// TODO: #176: new Language("La piña debe prohibirse en la pizza", "es"),
115+
}), parties[0].ObjectId, "some-image-uri-for-logo", isWriteIn: false),
116+
new Candidate("referendum-pineapple-negative", new InternationalizedText (
117+
new []{
118+
new Language("Pineapple should not be banned on pizza", "en"),
119+
// new Language("La piña no debe prohibirse en la pizza", "es"),
120+
}), parties[1].ObjectId, "some-image-uri-for-logo", isWriteIn: false)
121+
};
122+
123+
var contests = new List<ContestDescription>
124+
{
125+
// Justice Supreme Court
126+
new ContestDescription(
127+
objectId: "justice-supreme-court",
128+
electoralDistrictId: gpUnits[0].ObjectId, // jefferson-county
129+
sequenceOrder: 0,
130+
VoteVariationType.n_of_m,
131+
numberElected: 1,
132+
name: "Justice of the Supreme Court",
133+
selections: new[]
134+
{
135+
new SelectionDescription(
136+
"benjamin-franklin-selection",
137+
candidates[0].CandidateId, // benjamin-franklin
138+
sequenceOrder: 0),
139+
new SelectionDescription(
140+
"john-adams-selection",
141+
candidates[1].CandidateId, // john-adams
142+
sequenceOrder: 1),
143+
new SelectionDescription(
144+
"write-in-selection",
145+
candidates[2].CandidateId, // write-in
146+
sequenceOrder: 2)
147+
}),
148+
149+
// Pineapple Referendum
150+
new ContestDescription(
151+
objectId: "referendum-pineapple",
152+
electoralDistrictId: gpUnits[1].ObjectId, // jefferson-county-school-district-1
153+
sequenceOrder: 1,
154+
VoteVariationType.n_of_m,
155+
numberElected: 1,
156+
votesAllowed: 1,
157+
name: "The Pineapple Question",
158+
ballotTitle: new InternationalizedText (
159+
new []{
160+
new Language("Should pineapple be banned on pizza?", "en"),
161+
// TODO: #176: new Language("¿Debería prohibirse la piña en la pizza?", "es"),
162+
}),
163+
ballotSubtitle: new InternationalizedText (
164+
new []{
165+
new Language("The township considers this issue to be very important", "en"),
166+
// TODO: #176: new Language("El municipio considera que esta cuestión es muy importante", "es"),
167+
}),
168+
selections: new[]
169+
{
170+
new SelectionDescription(
171+
"referendum-pineapple-affirmative-selection",
172+
// Use Linq to lookup the value
173+
candidates.First(
174+
i => i.ObjectId == "referendum-pineapple-affirmative"
175+
).CandidateId,
176+
sequenceOrder: 0),
177+
new SelectionDescription(
178+
"referendum-pineapple-negative-selection",
179+
// Or just use the known string
180+
"referendum-pineapple-negative",
181+
sequenceOrder: 1)
182+
})
183+
};
184+
185+
var ballotStyles = new List<BallotStyle>
186+
{
187+
new BallotStyle(
188+
"jefferson-county-ballot-style",
189+
new[] { gpUnits[0].ObjectId }),
190+
new BallotStyle(
191+
"jefferson-county-school-district-1",
192+
new[] {
193+
gpUnits[0].ObjectId,
194+
gpUnits[1].ObjectId,
195+
})
196+
};
197+
198+
return new Manifest(
199+
"jefferson-county-open-primary",
200+
ElectionType.primary,
201+
startDate: DateTime.UtcNow,
202+
endDate: DateTime.UtcNow.AddDays(1),
203+
gpUnits.ToArray(),
204+
parties.ToArray(),
205+
candidates.ToArray(),
206+
contests.ToArray(),
207+
ballotStyles.ToArray()
208+
);
209+
}
18210
}
19211
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ namespace ElectionGuard
1919
using NativeCompactCiphertextBallot = NativeInterface.CompactCiphertextBallot.CompactCiphertextBallotHandle;
2020
using NativeSubmittedBallot = NativeInterface.SubmittedBallot.SubmittedBallotHandle;
2121

22+
/// <summary>
23+
/// Indicates the binary format
24+
/// </summary>
2225
public enum BinarySerializationEncoding
2326
{
2427
BSON,
@@ -412,7 +415,7 @@ public unsafe ElementModQ CryptoHashWith(ElementModQ encryptionSeed)
412415
return new ElementModQ(cryptoHash);
413416
}
414417

415-
/// <sumary>
418+
/// <summary>
416419
/// Given an encrypted BallotSelection, validates the encryption state against a specific seed hash and public key.
417420
/// Calling this function expects that the object is in a well-formed encrypted state
418421
/// with the elgamal encrypted `message` field populated along with
@@ -633,7 +636,7 @@ public unsafe ElementModQ CryptoHash
633636
}
634637

635638
/// <summary>
636-
/// The nonce used to generate the encryption. Sensitive & should be treated as a secret
639+
/// The nonce used to generate the encryption. Sensitive &amp; should be treated as a secret
637640
/// </summary>
638641
public unsafe ElementModQ Nonce
639642
{

0 commit comments

Comments
 (0)