diff options
Diffstat (limited to 'src/main/java/org/gnunet/voting/Ballot.java')
-rw-r--r-- | src/main/java/org/gnunet/voting/Ballot.java | 220 |
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; | |||
24 | import com.google.common.base.Optional; | 24 | import com.google.common.base.Optional; |
25 | import com.google.common.collect.BiMap; | 25 | import com.google.common.collect.BiMap; |
26 | import com.google.common.collect.HashBiMap; | 26 | import com.google.common.collect.HashBiMap; |
27 | import com.google.common.collect.Maps; | 27 | import com.google.common.primitives.Longs; |
28 | import org.gnunet.util.*; | 28 | import org.gnunet.util.*; |
29 | 29 | ||
30 | import java.security.MessageDigest; | 30 | import java.security.MessageDigest; |
@@ -38,18 +38,22 @@ import java.util.regex.Pattern; | |||
38 | public class Ballot { | 38 | public 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 | } |