aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/gnunet/voting/Ballot.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/gnunet/voting/Ballot.java')
-rw-r--r--src/main/java/org/gnunet/voting/Ballot.java220
1 files changed, 200 insertions, 20 deletions
diff --git a/src/main/java/org/gnunet/voting/Ballot.java b/src/main/java/org/gnunet/voting/Ballot.java
index 49f1465..0d8de27 100644
--- a/src/main/java/org/gnunet/voting/Ballot.java
+++ b/src/main/java/org/gnunet/voting/Ballot.java
@@ -24,7 +24,7 @@ import com.google.common.base.Joiner;
24import com.google.common.base.Optional; 24import com.google.common.base.Optional;
25import com.google.common.collect.BiMap; 25import com.google.common.collect.BiMap;
26import com.google.common.collect.HashBiMap; 26import com.google.common.collect.HashBiMap;
27import com.google.common.collect.Maps; 27import com.google.common.primitives.Longs;
28import org.gnunet.util.*; 28import org.gnunet.util.*;
29 29
30import java.security.MessageDigest; 30import java.security.MessageDigest;
@@ -38,18 +38,22 @@ import java.util.regex.Pattern;
38public class Ballot { 38public class Ballot {
39 39
40 String topic; 40 String topic;
41 String group;
41 List<String> choices; 42 List<String> choices;
42 AbsoluteTime electionStartTime; 43 AbsoluteTime startTime;
43 AbsoluteTime electionEndTime; 44 AbsoluteTime closingTime;
45 AbsoluteTime queryTime;
46 AbsoluteTime endTime;
44 BiMap<String,PeerIdentity> authorities; 47 BiMap<String,PeerIdentity> authorities;
45 SortedMap<String,CryptoECC.Signature> registrationSigs; 48 SortedMap<String,CryptoECC.Signature> registrationSigs;
46 CryptoECC.PublicKey caPub; 49 CryptoECC.PublicSignKey caPub;
47 CryptoECC.PublicKey issuerPub; 50 CryptoECC.PublicSignKey issuerPub;
48 CryptoECC.Signature issuerSig; 51 CryptoECC.Signature issuerSig;
49 CryptoECC.PublicKey voter_pub; 52 CryptoECC.PublicSignKey voterPub;
53 CryptoECC.Signature voterGroupCert;
50 CryptoECC.Signature permission; 54 CryptoECC.Signature permission;
51 CryptoECC.PublicKey voter_sig; 55 CryptoECC.PublicSignKey voterSig;
52 SortedMap<String,CryptoECC.Signature> vouchers; 56 SortedMap<String,CryptoECC.Signature> voucherSigs;
53 57
54 /** 58 /**
55 * Choice in plaintext. 59 * Choice in plaintext.
@@ -60,8 +64,8 @@ public class Ballot {
60 /** 64 /**
61 * Load a ballot from file. 65 * Load a ballot from file.
62 * 66 *
67 * @param filename name of file to load ballot from
63 * @throws InvalidBallotException 68 * @throws InvalidBallotException
64 * @param filename
65 */ 69 */
66 public Ballot(String filename) { 70 public Ballot(String filename) {
67 Configuration cfg = new Configuration(); 71 Configuration cfg = new Configuration();
@@ -69,10 +73,49 @@ public class Ballot {
69 fillBallot(cfg); 73 fillBallot(cfg);
70 } 74 }
71 75
76 /**
77 * Load a ballot from a configuration.
78 *
79 * @param cfg configuration to load ballot from
80 * @throws InvalidBallotException
81 */
72 public Ballot(Configuration cfg) { 82 public Ballot(Configuration cfg) {
73 fillBallot(cfg); 83 fillBallot(cfg);
74 } 84 }
75 85
86 /**
87 * Parse the specified time value from the ballot,
88 * converting a human-readable time value to human time when no
89 * timestamp is given.
90 * @param cfg configuration to read the value from
91 * @param timeValueName name suffix of the time option
92 * @return the time value
93 */
94 private AbsoluteTime getTime(Configuration cfg, String timeValueName) {
95 Optional<String> optTimeHuman = cfg.getValueString("election", "TIME_" + timeValueName);
96 Optional<String> optTimeStamp = cfg.getValueString("election", "TIMESTAMP_" + timeValueName);
97 if (optTimeStamp.isPresent()) {
98 try {
99 long stamp = Long.parseLong(optTimeStamp.get());
100 return AbsoluteTime.fromSeconds(stamp);
101 } catch (NumberFormatException e) {
102 throw new InvalidBallotException("time value " + timeValueName + " malformed");
103 }
104 } else if (optTimeHuman.isPresent()) {
105 AbsoluteTime time = AbsoluteTime.fromString(optTimeHuman.get());
106 if (null == time) {
107 throw new InvalidBallotException("timestamp value " + timeValueName + " malformed");
108 }
109 return time;
110 }
111 throw new InvalidBallotException("time value " + timeValueName + " missing");
112 }
113
114 /**
115 * Fill the ballot with information from the given configuration.
116 *
117 * @param cfg configuration to read from
118 */
76 private void fillBallot(Configuration cfg) { 119 private void fillBallot(Configuration cfg) {
77 Optional<String> optTopic = cfg.getValueString("election", "TOPIC"); 120 Optional<String> optTopic = cfg.getValueString("election", "TOPIC");
78 if (!optTopic.isPresent()) { 121 if (!optTopic.isPresent()) {
@@ -87,6 +130,11 @@ public class Ballot {
87 if (choices.size() < 2) { 130 if (choices.size() < 2) {
88 throw new InvalidBallotException("less than two choices present"); 131 throw new InvalidBallotException("less than two choices present");
89 } 132 }
133 Optional<String> optGroup = cfg.getValueString("election", "GROUP");
134 if (!optGroup.isPresent()) {
135 throw new InvalidBallotException("ballot must have elegibility group");
136 }
137 group = optGroup.get();
90 authorities = HashBiMap.create(); 138 authorities = HashBiMap.create();
91 for (Map.Entry<String,String> e : cfg.getSection("authorities").entrySet()) { 139 for (Map.Entry<String,String> e : cfg.getSection("authorities").entrySet()) {
92 String alias = e.getKey(); 140 String alias = e.getKey();
@@ -104,13 +152,13 @@ public class Ballot {
104 if (!optCaPub.isPresent()) { 152 if (!optCaPub.isPresent()) {
105 throw new InvalidBallotException("no CA pub key given"); 153 throw new InvalidBallotException("no CA pub key given");
106 } 154 }
107 caPub = CryptoECC.PublicKey.fromString(optCaPub.get()); 155 caPub = CryptoECC.PublicSignKey.fromString(optCaPub.get());
108 if (null == caPub) { 156 if (null == caPub) {
109 throw new InvalidBallotException("CA pub key invalid"); 157 throw new InvalidBallotException("CA pub key invalid");
110 } 158 }
111 Optional<String> optIssuerPub = cfg.getValueString("election", "ISSUER_PUB"); 159 Optional<String> optIssuerPub = cfg.getValueString("election", "ISSUER_PUB");
112 if (optIssuerPub.isPresent()) { 160 if (optIssuerPub.isPresent()) {
113 issuerPub = CryptoECC.PublicKey.fromString(optIssuerPub.get()); 161 issuerPub = CryptoECC.PublicSignKey.fromString(optIssuerPub.get());
114 Optional<String> optIssuerSig = cfg.getValueString("election", "ISSUER_SIG"); 162 Optional<String> optIssuerSig = cfg.getValueString("election", "ISSUER_SIG");
115 if (!optIssuerSig.isPresent()) { 163 if (!optIssuerSig.isPresent()) {
116 throw new InvalidBallotException("issuer public key present, but no signature"); 164 throw new InvalidBallotException("issuer public key present, but no signature");
@@ -128,6 +176,17 @@ public class Ballot {
128 } 176 }
129 registrationSigs.put(e.getKey(), sig); 177 registrationSigs.put(e.getKey(), sig);
130 } 178 }
179 voucherSigs = new TreeMap<String, CryptoECC.Signature>();
180 for (Map.Entry<String,String> e : cfg.getSection("vouchers").entrySet()) {
181 CryptoECC.Signature sig = CryptoECC.Signature.fromString(e.getValue());
182 if (null == sig) {
183 throw new InvalidBallotException("voucher signature has invalid format");
184 }
185 if (!authorities.containsKey(e.getKey())) {
186 throw new InvalidBallotException("ballot contains superfluous voucher signature");
187 }
188 voucherSigs.put(e.getKey(), sig);
189 }
131 Optional<String> optChoiceId = cfg.getValueString("vote", "CHOICE_ID"); 190 Optional<String> optChoiceId = cfg.getValueString("vote", "CHOICE_ID");
132 if (optChoiceId.isPresent()) { 191 if (optChoiceId.isPresent()) {
133 choiceId = Integer.parseInt(optChoiceId.get()); 192 choiceId = Integer.parseInt(optChoiceId.get());
@@ -137,11 +196,27 @@ public class Ballot {
137 } else { 196 } else {
138 choiceId = -1; 197 choiceId = -1;
139 } 198 }
199
200 Optional<String> optVoterPub = cfg.getValueString("vote", "VOTER_PUB");
201 if (optVoterPub.isPresent()) {
202 voterPub = CryptoECC.PublicSignKey.fromString(optVoterPub.get());
203 }
204
205 startTime = getTime(cfg, "START");
206 closingTime = getTime(cfg, "CLOSING");
207 queryTime = getTime(cfg, "QUERY");
208 endTime = getTime(cfg, "END");
140 } 209 }
141 210
211 /**
212 * Get a hash code that uniquely defines the election information of
213 * the ballot.
214 *
215 * @return GUID of this ballot
216 */
142 public HashCode getBallotGuid() { 217 public HashCode getBallotGuid() {
143 if (issuerSig == null) { 218 if (issuerPub == null) {
144 throw new InvalidBallotException("can't compute GUID of non-issued ballot"); 219 throw new InvalidBallotException("can't compute GUID of a ballot without issuer");
145 } 220 }
146 MessageDigest digest; 221 MessageDigest digest;
147 try { 222 try {
@@ -150,8 +225,6 @@ public class Ballot {
150 throw new RuntimeException("crypto algorithm required but not provided"); 225 throw new RuntimeException("crypto algorithm required but not provided");
151 } 226 }
152 digest.update(topic.getBytes()); 227 digest.update(topic.getBytes());
153 //digest.update(Longs.toByteArray(electionStartTime.getSeconds()));
154 //digest.update(Longs.toByteArray(electionEndTime.getSeconds()));
155 for (String choice : choices) { 228 for (String choice : choices) {
156 digest.update(choice.getBytes()); 229 digest.update(choice.getBytes());
157 } 230 }
@@ -163,11 +236,16 @@ public class Ballot {
163 digest.update(issuerPub.y); 236 digest.update(issuerPub.y);
164 digest.update(caPub.x); 237 digest.update(caPub.x);
165 digest.update(caPub.y); 238 digest.update(caPub.y);
239 digest.update(Longs.toByteArray(startTime.getSeconds()));
240 digest.update(Longs.toByteArray(endTime.getSeconds()));
241 digest.update(Longs.toByteArray(closingTime.getSeconds()));
242 digest.update(Longs.toByteArray(queryTime.getSeconds()));
166 return new HashCode(digest.digest()); 243 return new HashCode(digest.digest());
167 } 244 }
168 245
169 /** 246 /**
170 * Encode the given choice permanently in the ballot. 247 * Encode the given choice permanently in the ballot.
248 * Also encodes the voter's public key.
171 * 249 *
172 * @param choice the choice to encode the ballot 250 * @param choice the choice to encode the ballot
173 * @param privateKey the private key to use for encoding 251 * @param privateKey the private key to use for encoding
@@ -181,21 +259,39 @@ public class Ballot {
181 } 259 }
182 i++; 260 i++;
183 } 261 }
262 voterPub = CryptoECC.computePublicKey(privateKey);
184 if (choiceId == -1) { 263 if (choiceId == -1) {
185 throw new InvalidBallotException(String.format("choice '%s' not valid", choice)); 264 throw new InvalidBallotException(String.format("choice '%s' not valid", choice));
186 } 265 }
187 } 266 }
188 267
268 /**
269 * Write the current state of the ballot to a configuration.
270 *
271 * @return a configuration with the state of this ballot
272 */
189 public Configuration toConfiguration() { 273 public Configuration toConfiguration() {
190 Configuration cfg = new Configuration(); 274 Configuration cfg = new Configuration();
191 cfg.setValueString("election", "TOPIC", topic); 275 cfg.setValueString("election", "TOPIC", topic);
276 cfg.setValueString("election", "GROUP", group);
192 cfg.setValueString("election", "CHOICES", Joiner.on("//").join(choices)); 277 cfg.setValueString("election", "CHOICES", Joiner.on("//").join(choices));
193 cfg.setValueString("election", "CA_PUB", caPub.toString()); 278 cfg.setValueString("election", "CA_PUB", caPub.toString());
279 cfg.setValueNumber("election", "TIMESTAMP_START", startTime.getSeconds());
280 cfg.setValueNumber("election", "TIMESTAMP_CLOSING", startTime.getSeconds());
281 cfg.setValueNumber("election", "TIMESTAMP_QUERY", startTime.getSeconds());
282 cfg.setValueNumber("election", "TIMESTAMP_END", startTime.getSeconds());
194 for (Map.Entry<String, PeerIdentity> e : authorities.entrySet()) { 283 for (Map.Entry<String, PeerIdentity> e : authorities.entrySet()) {
195 cfg.setValueString("authorities", e.getKey(), e.getValue().toString()); 284 cfg.setValueString("authorities", e.getKey(), e.getValue().toString());
196 } 285 }
197 for (Map.Entry<String, CryptoECC.Signature> e : registrationSigs.entrySet()) { 286 if (null != registrationSigs) {
198 cfg.setValueString("registration-signatures", e.getKey(), e.getValue().toString()); 287 for (Map.Entry<String, CryptoECC.Signature> e : registrationSigs.entrySet()) {
288 cfg.setValueString("registration-signatures", e.getKey(), e.getValue().toString());
289 }
290 }
291 if (null != voucherSigs) {
292 for (Map.Entry<String, CryptoECC.Signature> e : voucherSigs.entrySet()) {
293 cfg.setValueString("vouchers", e.getKey(), e.getValue().toString());
294 }
199 } 295 }
200 if (null != issuerPub) { 296 if (null != issuerPub) {
201 cfg.setValueString("election", "ISSUER_PUB", issuerPub.toString()); 297 cfg.setValueString("election", "ISSUER_PUB", issuerPub.toString());
@@ -204,19 +300,52 @@ public class Ballot {
204 if (-1 != choiceId) { 300 if (-1 != choiceId) {
205 cfg.setValueNumber("vote", "CHOICE_ID", choiceId); 301 cfg.setValueNumber("vote", "CHOICE_ID", choiceId);
206 } 302 }
303 if (null != voterPub) {
304 cfg.setValueString("vote", "VOTER_PUB", voterPub.toString());
305 }
207 return cfg; 306 return cfg;
208 } 307 }
209 308
309 /**
310 * Serialize the ballot to a string
311 *
312 * @return the serialized ballot
313 */
210 public String serialize() { 314 public String serialize() {
211 return toConfiguration().serialize(); 315 return toConfiguration().serialize();
212 } 316 }
213 317
318
319 /**
320 * Get a human readable description of the ballot's current state.
321 *
322 * @return the ballot description
323 */
214 public String describe() { 324 public String describe() {
215 StringBuilder buf = new StringBuilder(); 325 StringBuilder buf = new StringBuilder();
326
216 buf.append("Topic: "); 327 buf.append("Topic: ");
217 buf.append("'"); 328 buf.append("'");
218 buf.append(topic); 329 buf.append(topic);
219 buf.append("'\n"); 330 buf.append("'\n");
331
332 buf.append("Voter Group: ");
333 buf.append(group);
334 buf.append("\n");
335
336 buf.append("Start Time: ");
337 buf.append(startTime.toFancyString());
338 buf.append("\n");
339 buf.append("Closing Time: ");
340 buf.append(closingTime.toFancyString());
341 buf.append("\n");
342 buf.append("Query Time: ");
343 buf.append(queryTime.toFancyString());
344 buf.append("\n");
345 buf.append("End Time: ");
346 buf.append(endTime.toFancyString());
347 buf.append("\n");
348
220 buf.append("Choices:\n"); 349 buf.append("Choices:\n");
221 for (int i = 0; i < choices.size(); i++) { 350 for (int i = 0; i < choices.size(); i++) {
222 buf.append(""+(i+1)); 351 buf.append(""+(i+1));
@@ -247,6 +376,14 @@ public class Ballot {
247 } 376 }
248 buf.append("\n"); 377 buf.append("\n");
249 } 378 }
379 if (!voucherSigs.isEmpty()) {
380 buf.append("ballot's vote has been submitted to with the following authorities:\n");
381 for (Map.Entry<String, CryptoECC.Signature> e : voucherSigs.entrySet()) {
382 buf.append(e.getKey());
383 buf.append(" ");
384 }
385 buf.append("\n");
386 }
250 else { 387 else {
251 buf.append("ballot not registered\n"); 388 buf.append("ballot not registered\n");
252 } 389 }
@@ -258,12 +395,27 @@ public class Ballot {
258 return buf.toString(); 395 return buf.toString();
259 } 396 }
260 397
398
399 /**
400 * Get the list of authorities that did not yet receive the ballot's vote.
401 *
402 * @return list of unsubmitted-to authorities
403 */
261 public List<PeerIdentity> getRemainingSubmitAuthorities() { 404 public List<PeerIdentity> getRemainingSubmitAuthorities() {
262 // FIXME: only return remaining authorities 405 LinkedList<PeerIdentity> remaining = new LinkedList<PeerIdentity>();
263 return new ArrayList<PeerIdentity>(authorities.values()); 406 for (SortedMap.Entry<String,PeerIdentity> x : authorities.entrySet()) {
407 if (!voucherSigs.containsKey(x.getKey()))
408 remaining.add(x.getValue());
409 }
410 return remaining;
264 } 411 }
265 412
266 413 /**
414 * Get a list of authorities that did not receive the registration for this
415 * ballot yet.
416 *
417 * @return list of authorities that don't know about this ballot
418 */
267 public List<PeerIdentity> getRemainingRegisterAuthorities() { 419 public List<PeerIdentity> getRemainingRegisterAuthorities() {
268 LinkedList<PeerIdentity> remaining = new LinkedList<PeerIdentity>(); 420 LinkedList<PeerIdentity> remaining = new LinkedList<PeerIdentity>();
269 for (SortedMap.Entry<String,PeerIdentity> x : authorities.entrySet()) { 421 for (SortedMap.Entry<String,PeerIdentity> x : authorities.entrySet()) {
@@ -273,16 +425,44 @@ public class Ballot {
273 return remaining; 425 return remaining;
274 } 426 }
275 427
428 /**
429 * Add the issuer to the ballot, and sign the election information in the ballot.
430 *
431 * @param privateKey private key of the issuer
432 */
276 public void issue(CryptoECC.PrivateKey privateKey) { 433 public void issue(CryptoECC.PrivateKey privateKey) {
277 issuerPub = CryptoECC.computePublicKey(privateKey); 434 issuerPub = CryptoECC.computePublicKey(privateKey);
278 issuerSig = CryptoECC.sign(getBallotGuid().data, privateKey, issuerPub); 435 issuerSig = CryptoECC.sign(getBallotGuid().data, privateKey, issuerPub);
279 } 436 }
280 437
438 /**
439 * Add an authorities registration signature to the list of known registrations.
440 *
441 * @param currentAuthority authority we registered with
442 * @param registrationSignature signature over this ballot's GUID from the authority
443 */
281 public void addRegistrationSignature(PeerIdentity currentAuthority, CryptoECC.Signature registrationSignature) { 444 public void addRegistrationSignature(PeerIdentity currentAuthority, CryptoECC.Signature registrationSignature) {
282 String alias = authorities.inverse().get(currentAuthority); 445 String alias = authorities.inverse().get(currentAuthority);
283 registrationSigs.put(alias, registrationSignature); 446 registrationSigs.put(alias, registrationSignature);
284 } 447 }
285 448
449 /**
450 * Add a voucher, that is, a signature about the fact that an authority received a vote,
451 * to the list of vouchers
452 *
453 * @param currentAuthority authority that received the vote
454 * @param voucherSignature signature from the authority
455 */
456 public void addVoucher(PeerIdentity currentAuthority, CryptoECC.Signature voucherSignature) {
457 String alias = authorities.inverse().get(currentAuthority);
458 voucherSigs.put(alias, voucherSignature);
459 }
460
461 /**
462 * Get a list of all authorities.
463 *
464 * @return list of all authorities
465 */
286 public List<PeerIdentity> getAuthorities() { 466 public List<PeerIdentity> getAuthorities() {
287 return new ArrayList<PeerIdentity>(authorities.values()); 467 return new ArrayList<PeerIdentity>(authorities.values());
288 } 468 }