1 {-# LANGUAGE DeriveAnyClass #-}
2 {-# LANGUAGE DeriveGeneric #-}
3 {-# LANGUAGE OverloadedStrings #-}
4 module Voting.Protocol.Election where
6 import Control.DeepSeq (NFData)
7 import Control.Monad (Monad(..), join, mapM, replicateM, unless, zipWithM)
8 import Control.Monad.Trans.Class (MonadTrans(..))
9 import Control.Monad.Trans.Except (Except, ExceptT, runExcept, throwE, withExceptT)
11 import Data.Either (either)
12 import Data.Eq (Eq(..))
13 import Data.Foldable (Foldable, foldMap, and)
14 import Data.Function (($), id, const)
15 import Data.Functor (Functor, (<$>))
16 import Data.Functor.Identity (Identity(..))
17 import Data.Maybe (Maybe(..), fromMaybe, maybe)
18 import Data.Ord (Ord(..))
19 import Data.Semigroup (Semigroup(..))
20 import Data.Text (Text)
21 import Data.Traversable (Traversable(..))
22 import Data.Tuple (fst, snd, uncurry)
23 import GHC.Natural (minusNaturalMaybe)
24 import GHC.Generics (Generic)
25 import Numeric.Natural (Natural)
26 import Prelude (fromIntegral)
27 import Text.Show (Show(..))
28 import qualified Control.Monad.Trans.State.Strict as S
29 import qualified Data.ByteString as BS
30 import qualified Data.List as List
31 import qualified Data.Map.Strict as Map
33 import Voting.Protocol.Utils
34 import Voting.Protocol.Arithmetic
35 import Voting.Protocol.Credential
37 -- * Type 'Encryption'
38 -- | ElGamal-like encryption.
39 -- Its security relies on the /Discrete Logarithm problem/.
41 -- Because ('groupGen' '^'encNonce '^'secKey '==' 'groupGen' '^'secKey '^'encNonce),
42 -- knowing @secKey@, one can divide 'encryption_vault' by @('encryption_nonce' '^'secKey)@
43 -- to decipher @('groupGen' '^'clear)@, then the @clear@ text must be small to be decryptable,
44 -- because it is encrypted as a power of 'groupGen' (hence the "-like" in "ElGamal-like")
45 -- to enable the additive homomorphism.
47 -- NOTE: Since @('encryption_vault' '*' 'encryption_nonce' '==' 'encryption_nonce' '^' (secKey '+' clear))@,
48 -- then: @(logBase 'encryption_nonce' ('encryption_vault' '*' 'encryption_nonce') '==' secKey '+' clear)@.
49 data Encryption q = Encryption
50 { encryption_nonce :: G q
51 -- ^ Public part of the randomness 'encNonce' used to 'encrypt' the 'clear' text,
52 -- equal to @('groupGen' '^'encNonce)@
53 , encryption_vault :: G q
54 -- ^ Encrypted 'clear' text,
55 -- equal to @('pubKey' '^'encNone '*' 'groupGen' '^'clear)@
56 } deriving (Eq,Show,Generic,NFData)
58 -- | Additive homomorphism.
59 -- Using the fact that: @'groupGen' '^'x '*' 'groupGen' '^'y '==' 'groupGen' '^'(x'+'y)@.
60 instance SubGroup q => Additive (Encryption q) where
61 zero = Encryption one one
63 (encryption_nonce x * encryption_nonce y)
64 (encryption_vault x * encryption_vault y)
66 -- *** Type 'EncryptionNonce'
67 type EncryptionNonce = E
69 -- | @('encrypt' pubKey clear)@ returns an ElGamal-like 'Encryption'.
71 -- WARNING: the secret encryption nonce (@encNonce@)
72 -- is returned alongside the 'Encryption'
73 -- in order to 'prove' the validity of the encrypted 'clear' text in 'proveEncryption',
74 -- but this secret @encNonce@ MUST be forgotten after that,
75 -- as it may be used to decipher the 'Encryption'
76 -- without the 'SecretKey' associated with 'pubKey'.
78 Monad m => RandomGen r => SubGroup q =>
80 S.StateT r m (EncryptionNonce q, Encryption q)
81 encrypt pubKey clear = do
83 -- NOTE: preserve the 'encNonce' for 'prove' in 'proveEncryption'.
86 { encryption_nonce = groupGen^encNonce
87 , encryption_vault = pubKey ^encNonce * groupGen^clear
91 -- | 'Proof' of knowledge of a discrete logarithm:
92 -- @(secret == logBase base (base^secret))@.
94 { proof_challenge :: Challenge q
95 -- ^ 'Challenge' sent by the verifier to the prover
96 -- to ensure that the prover really has knowledge
97 -- of the secret and is not replaying.
98 -- Actually, 'proof_challenge' is not sent to the prover,
99 -- but derived from the prover's 'Commitment's and statements
100 -- with a collision resistant 'hash'.
101 -- Hence the prover cannot chose the 'proof_challenge' to his/her liking.
102 , proof_response :: E q
103 -- ^ A discrete logarithm sent by the prover to the verifier,
104 -- as a response to 'proof_challenge'.
106 -- If the verifier observes that @('proof_challenge' '==' 'hash' statement [commitment])@, where:
108 -- * @statement@ is a serialization of a tag, @base@ and @basePowSec@,
109 -- * @commitment '==' 'commit' proof base basePowSec '=='
110 -- base '^' 'proof_response' '*' basePowSec '^' 'proof_challenge'@,
111 -- * and @basePowSec '==' base'^'sec@,
113 -- then, with overwhelming probability (due to the 'hash' function),
114 -- the prover was not able to choose 'proof_challenge'
115 -- yet was able to compute a 'proof_response' such that
116 -- (@commitment '==' base '^' 'proof_response' '*' basePowSec '^' 'proof_challenge'@),
117 -- that is to say: @('proof_response' '==' logBase base 'commitment' '-' sec '*' 'proof_challenge')@,
118 -- therefore the prover knows 'sec'.
120 -- The prover choses 'commitment' to be a random power of @base@,
121 -- to ensure that each 'prove' does not reveal any information
123 } deriving (Eq,Show,Generic,NFData)
126 -- | Zero-knowledge proof.
128 -- A protocol is /zero-knowledge/ if the verifier
129 -- learns nothing from the protocol except that the prover
132 -- DOC: Mihir Bellare and Phillip Rogaway. Random oracles are practical:
133 -- A paradigm for designing efficient protocols. In ACM-CCS’93, 1993.
134 newtype ZKP = ZKP BS.ByteString
136 -- ** Type 'Challenge'
140 -- An 'Oracle' returns the 'Challenge' of the 'Commitment's
141 -- by 'hash'ing them (eventually with other 'Commitment's).
143 -- Used in 'prove' it enables a Fiat-Shamir transformation
144 -- of an /interactive zero-knowledge/ (IZK) proof
145 -- into a /non-interactive zero-knowledge/ (NIZK) proof.
146 -- That is to say that the verifier does not have
147 -- to send a 'Challenge' to the prover.
148 -- Indeed, the prover now handles the 'Challenge'
149 -- which becomes a (collision resistant) 'hash'
150 -- of the prover's commitments (and statements to be a stronger proof).
151 type Oracle list q = list (Commitment q) -> Challenge q
153 -- | @('prove' sec commitBases oracle)@
154 -- returns a 'Proof' that @sec@ is known
155 -- (by proving the knowledge of its discrete logarithm).
157 -- The 'Oracle' is given 'Commitment's equal to the 'commitBases'
158 -- raised to the power of the secret nonce of the 'Proof',
159 -- as those are the 'Commitment's that the verifier will obtain
160 -- when composing the 'proof_challenge' and 'proof_response' together
163 -- WARNING: for 'prove' to be a so-called /strong Fiat-Shamir transformation/ (not a weak):
164 -- the statement must be included in the 'hash' (along with the commitments).
166 -- NOTE: a 'random' @nonce@ is used to ensure each 'prove'
167 -- does not reveal any information regarding the secret @sec@,
168 -- because two 'Proof's using the same 'Commitment'
169 -- can be used to deduce @sec@ (using the special-soundness).
171 Monad m => RandomGen r => SubGroup q => Functor list =>
172 E q -> list (G q) -> Oracle list q -> S.StateT r m (Proof q)
173 prove sec commitBases oracle = do
175 let commitments = (^ nonce) <$> commitBases
176 let proof_challenge = oracle commitments
179 , proof_response = nonce - sec*proof_challenge
182 -- | @('fakeProof')@ returns a 'Proof'
183 -- whose 'proof_challenge' and 'proof_response' are uniformly chosen at random,
184 -- instead of @('proof_challenge' '==' 'hash' statement commitments)@
185 -- and @('proof_response' '==' nonce '+' sec '*' 'proof_challenge')@
186 -- as a 'Proof' returned by 'prove'.
188 -- Used in 'proveEncryption' to fill the returned 'DisjProof'
189 -- with fake 'Proof's for all 'Disjunction's but the encrypted one.
190 fakeProof :: Monad m => RandomGen r => SubGroup q => S.StateT r m (Proof q)
192 proof_challenge <- random
193 proof_response <- random
196 -- ** Type 'Commitment'
197 -- | A commitment from the prover to the verifier.
198 -- It's a power of 'groupGen' chosen randomly by the prover
199 -- when making a 'Proof' with 'prove'.
202 -- | @('commit' proof base basePowSec)@ returns a 'Commitment'
203 -- from the given 'Proof' with the knowledge of the verifier.
204 commit :: SubGroup q => Proof q -> G q -> G q -> Commitment q
205 commit Proof{..} base basePowSec =
206 base^proof_response *
207 basePowSec^proof_challenge
208 -- NOTE: Contrary to some textbook presentations,
209 -- @('*')@ is used instead of @('/')@ to avoid the performance cost
210 -- of a modular exponentiation @('^' ('groupOrder' '-' 'one'))@,
211 -- this is compensated by using @('-')@ instead of @('+')@ in 'prove'.
212 {-# INLINE commit #-}
214 -- * Type 'Disjunction'
215 -- | A 'Disjunction' is an 'inv'ersed @('groupGen' '^'opinion)@
216 -- it's used in 'proveEncryption' to generate a 'Proof'
217 -- that an 'encryption_vault' contains a given @('groupGen' '^'opinion)@,
220 booleanDisjunctions :: SubGroup q => [Disjunction q]
221 booleanDisjunctions = List.take 2 groupGenInverses
223 intervalDisjunctions :: SubGroup q => Opinion q -> Opinion q -> [Disjunction q]
224 intervalDisjunctions mini maxi =
225 List.genericTake (fromMaybe 0 $ (nat maxi + 1)`minusNaturalMaybe`nat mini) $
226 List.genericDrop (nat mini) $
230 -- | Index of a 'Disjunction' within a list of them.
231 -- It is encrypted as an 'E'xponent by 'encrypt'.
234 -- ** Type 'DisjProof'
235 -- | A list of 'Proof's to prove that the 'Opinion' within an 'Encryption'
236 -- is indexing a 'Disjunction' within a list of them,
237 -- without revealing which 'Opinion' it is.
238 newtype DisjProof q = DisjProof [Proof q]
239 deriving (Eq,Show,Generic,NFData)
241 -- | @('proveEncryption' elecPubKey voterZKP (prevDisjs,nextDisjs) (encNonce,enc))@
242 -- returns a 'DisjProof' that 'enc' 'encrypt's
243 -- the 'Disjunction' 'd' between 'prevDisjs' and 'nextDisjs'.
245 -- The prover proves that it knows an 'encNonce', such that:
246 -- @(enc '==' Encryption{encryption_nonce='groupGen' '^'encNonce, encryption_vault=elecPubKey'^'encNonce '*' groupGen'^'d})@
248 -- A /NIZK Disjunctive Chaum Pedersen Logarithm Equality/ is used.
250 -- DOC: Pierrick Gaudry. <https://hal.inria.fr/hal-01576379 Some ZK security proofs for Belenios>, 2017.
252 Monad m => RandomGen r => SubGroup q =>
253 PublicKey q -> ZKP ->
254 ([Disjunction q],[Disjunction q]) ->
255 (EncryptionNonce q, Encryption q) ->
256 S.StateT r m (DisjProof q)
257 proveEncryption elecPubKey voterZKP (prevDisjs,nextDisjs) (encNonce,enc) = do
258 -- Fake proofs for all 'Disjunction's except the genuine one.
259 prevFakeProofs <- replicateM (List.length prevDisjs) fakeProof
260 nextFakeProofs <- replicateM (List.length nextDisjs) fakeProof
261 let fakeChallengeSum =
262 sum (proof_challenge <$> prevFakeProofs) +
263 sum (proof_challenge <$> nextFakeProofs)
264 let statement = encryptionStatement voterZKP enc
265 genuineProof <- prove encNonce [groupGen, elecPubKey] $ \genuineCommitments ->
266 let validCommitments = List.zipWith (encryptionCommitments elecPubKey enc) in
267 let prevCommitments = validCommitments prevDisjs prevFakeProofs in
268 let nextCommitments = validCommitments nextDisjs nextFakeProofs in
269 let commitments = join prevCommitments <> genuineCommitments <> join nextCommitments in
270 let challenge = hash statement commitments in
271 let genuineChallenge = challenge - fakeChallengeSum in
273 -- NOTE: here by construction (genuineChallenge == challenge - fakeChallengeSum)
274 -- thus (sum (proof_challenge <$> proofs) == challenge)
275 -- as checked in 'verifyEncryption'.
276 let proofs = prevFakeProofs <> (genuineProof : nextFakeProofs)
277 return (DisjProof proofs)
280 Monad m => SubGroup q =>
281 PublicKey q -> ZKP ->
282 [Disjunction q] -> (Encryption q, DisjProof q) ->
283 ExceptT ErrorVerifyEncryption m Bool
284 verifyEncryption elecPubKey voterZKP disjs (enc, DisjProof proofs) =
285 case isoZipWith (encryptionCommitments elecPubKey enc) disjs proofs of
287 throwE $ ErrorVerifyEncryption_InvalidProofLength
288 (fromIntegral $ List.length proofs)
289 (fromIntegral $ List.length disjs)
291 return $ challengeSum ==
292 hash (encryptionStatement voterZKP enc) (join commitments)
294 challengeSum = sum (proof_challenge <$> proofs)
297 encryptionStatement :: SubGroup q => ZKP -> Encryption q -> BS.ByteString
298 encryptionStatement (ZKP voterZKP) Encryption{..} =
299 "prove|"<>voterZKP<>"|"
300 <> bytesNat encryption_nonce<>","
301 <> bytesNat encryption_vault<>"|"
303 -- | @('encryptionCommitments' elecPubKey enc disj proof)@
304 -- returns the 'Commitment's with only the knowledge of the verifier.
306 -- For the prover the 'Proof' comes from @fakeProof@,
307 -- and for the verifier the 'Proof' comes from the prover.
308 encryptionCommitments ::
310 PublicKey q -> Encryption q ->
311 Disjunction q -> Proof q -> [G q]
312 encryptionCommitments elecPubKey Encryption{..} disj proof =
313 [ commit proof groupGen encryption_nonce
314 -- == groupGen ^ nonce if 'Proof' comes from 'prove'.
315 -- base==groupGen, basePowSec==groupGen^encNonce.
316 , commit proof elecPubKey (encryption_vault*disj)
317 -- == elecPubKey ^ nonce if 'Proof' comes from 'prove'
318 -- and 'encryption_vault' encrypts (- logBase groupGen disj).
319 -- base==elecPubKey, basePowSec==elecPubKey^encNonce.
322 -- ** Type 'ErrorVerifyEncryption'
323 -- | Error raised by 'verifyEncryption'.
324 data ErrorVerifyEncryption
325 = ErrorVerifyEncryption_InvalidProofLength Natural Natural
326 -- ^ When the number of proofs is different than
327 -- the number of 'Disjunction's.
331 data Question q = Question
332 { question_text :: Text
333 , question_choices :: [Text]
334 , question_mini :: Opinion q
335 , question_maxi :: Opinion q
336 -- , question_blank :: Maybe Bool
337 } deriving (Eq,Show,Generic,NFData)
340 data Answer q = Answer
341 { answer_opinions :: [(Encryption q, DisjProof q)]
342 -- ^ Encrypted 'Opinion' for each 'question_choices'
343 -- with a 'DisjProof' that they belong to [0,1].
344 , answer_sumProof :: DisjProof q
345 -- ^ Proofs that the sum of the 'Opinon's encrypted in 'answer_opinions'
346 -- is an element of @[mini..maxi]@.
347 -- , answer_blankProof ::
348 } deriving (Eq,Show,Generic,NFData)
350 -- | @('encryptAnswer' elecPubKey zkp quest opinions)@
351 -- returns an 'Answer' validable by 'verifyAnswer',
352 -- unless an 'ErrorAnswer' is returned.
354 Monad m => RandomGen r => SubGroup q =>
355 PublicKey q -> ZKP ->
356 Question q -> [Bool] ->
357 S.StateT r (ExceptT ErrorAnswer m) (Answer q)
358 encryptAnswer elecPubKey zkp Question{..} opinionByChoice
359 | not (question_mini <= opinionsSum && opinionsSum <= question_maxi) =
361 ErrorAnswer_WrongSumOfOpinions
365 | List.length opinions /= List.length question_choices =
367 ErrorAnswer_WrongNumberOfOpinions
368 (fromIntegral $ List.length opinions)
369 (fromIntegral $ List.length question_choices)
371 encryptions <- encrypt elecPubKey `mapM` opinions
372 individualProofs <- zipWithM
373 (\opinion -> proveEncryption elecPubKey zkp $
375 then ([booleanDisjunctions List.!!0],[])
376 else ([],[booleanDisjunctions List.!!1]))
377 opinionByChoice encryptions
378 sumProof <- proveEncryption elecPubKey zkp
379 (List.tail <$> List.genericSplitAt
380 (nat (opinionsSum - question_mini))
381 (intervalDisjunctions question_mini question_maxi))
382 ( sum (fst <$> encryptions) -- NOTE: sum the 'encNonce's
383 , sum (snd <$> encryptions) -- NOTE: sum the 'Encryption's
386 { answer_opinions = List.zip
387 (snd <$> encryptions) -- NOTE: drop encNonce
389 , answer_sumProof = sumProof
392 opinionsSum = sum opinions
393 opinions = (\o -> if o then one else zero) <$> opinionByChoice
397 PublicKey q -> ZKP ->
398 Question q -> Answer q -> Bool
399 verifyAnswer elecPubKey zkp Question{..} Answer{..}
400 | List.length question_choices /= List.length answer_opinions = False
401 | otherwise = either (const False) id $ runExcept $ do
403 verifyEncryption elecPubKey zkp booleanDisjunctions
404 `traverse` answer_opinions
405 validSum <- verifyEncryption elecPubKey zkp
406 (intervalDisjunctions question_mini question_maxi)
407 ( sum (fst <$> answer_opinions)
409 return (and validOpinions && validSum)
411 -- ** Type 'ErrorAnswer'
412 -- | Error raised by 'encryptAnswer'.
414 = ErrorAnswer_WrongNumberOfOpinions Natural Natural
415 -- ^ When the number of opinions is different than
416 -- the number of choices ('question_choices').
417 | ErrorAnswer_WrongSumOfOpinions Natural Natural Natural
418 -- ^ When the sum of opinions is not within the bounds
419 -- of 'question_mini' and 'question_maxi'.
420 deriving (Eq,Show,Generic,NFData)
423 data Election q = Election
424 { election_name :: Text
425 , election_description :: Text
426 , election_publicKey :: PublicKey q
427 , election_questions :: [Question q]
428 , election_uuid :: UUID
429 , election_hash :: Hash -- TODO: serialize to JSON to calculate this
430 } deriving (Eq,Show,Generic,NFData)
433 newtype Hash = Hash Text
434 deriving (Eq,Ord,Show,Generic,NFData)
437 data Ballot q = Ballot
438 { ballot_answers :: [Answer q]
439 , ballot_signature :: Maybe (Signature q)
440 , ballot_election_uuid :: UUID
441 , ballot_election_hash :: Hash
442 } deriving (Generic,NFData)
444 -- | @('encryptBallot' elec ('Just' ballotSecKey) opinionsByQuest)@
445 -- returns a 'Ballot' signed by 'secKey' (the voter's secret key)
446 -- where 'opinionsByQuest' is a list of 'Opinion's
447 -- on each 'question_choices' of each 'election_questions'.
449 Monad m => RandomGen r => SubGroup q =>
450 Election q -> Maybe (SecretKey q) -> [[Bool]] ->
451 S.StateT r (ExceptT ErrorBallot m) (Ballot q)
452 encryptBallot Election{..} ballotSecKeyMay opinionsByQuest
453 | List.length election_questions /= List.length opinionsByQuest =
455 ErrorBallot_WrongNumberOfAnswers
456 (fromIntegral $ List.length opinionsByQuest)
457 (fromIntegral $ List.length election_questions)
459 let (voterKeys, voterZKP) =
460 case ballotSecKeyMay of
461 Nothing -> (Nothing, ZKP "")
463 ( Just (ballotSecKey, ballotPubKey)
464 , ZKP (bytesNat ballotPubKey) )
465 where ballotPubKey = publicKey ballotSecKey
467 S.mapStateT (withExceptT ErrorBallot_Answer) $
468 zipWithM (encryptAnswer election_publicKey voterZKP)
469 election_questions opinionsByQuest
470 ballot_signature <- case voterKeys of
471 Nothing -> return Nothing
472 Just (ballotSecKey, signature_publicKey) -> do
474 prove ballotSecKey (Identity groupGen) $
475 \(Identity commitment) ->
477 -- NOTE: the order is unusual, the commitments are first
478 -- then comes the statement. Best guess is that
479 -- this is easier to code due to their respective types.
480 (signatureCommitments voterZKP commitment)
481 (signatureStatement ballot_answers)
482 return $ Just Signature{..}
485 , ballot_election_hash = election_hash
486 , ballot_election_uuid = election_uuid
490 verifyBallot :: SubGroup q => Election q -> Ballot q -> Bool
491 verifyBallot Election{..} Ballot{..} =
492 ballot_election_uuid == election_uuid &&
493 ballot_election_hash == election_hash &&
494 List.length election_questions == List.length ballot_answers &&
495 let (isValidSign, zkpSign) =
496 case ballot_signature of
497 Nothing -> (True, ZKP "")
498 Just Signature{..} ->
499 let zkp = ZKP (bytesNat signature_publicKey) in
501 proof_challenge signature_proof == hash
502 (signatureCommitments zkp (commit signature_proof groupGen signature_publicKey))
503 (signatureStatement ballot_answers)
506 List.zipWith (verifyAnswer election_publicKey zkpSign)
507 election_questions ballot_answers
509 -- ** Type 'Signature'
510 -- | Schnorr-like signature.
512 -- Used by each voter to sign his/her encrypted 'Ballot'
513 -- using his/her 'Credential',
514 -- in order to avoid ballot stuffing.
515 data Signature q = Signature
516 { signature_publicKey :: PublicKey q
517 -- ^ Verification key.
518 , signature_proof :: Proof q
519 } deriving (Generic,NFData)
523 -- | @('signatureStatement' answers)@
524 -- returns the encrypted material to be signed:
525 -- all the 'encryption_nonce's and 'encryption_vault's of the given @answers@.
526 signatureStatement :: Foldable f => SubGroup q => f (Answer q) -> [G q]
528 foldMap $ \Answer{..} ->
529 (`foldMap` answer_opinions) $ \(Encryption{..}, _proof) ->
530 [encryption_nonce, encryption_vault]
532 -- | @('signatureCommitments' voterZKP commitment)@
533 signatureCommitments :: SubGroup q => ZKP -> Commitment q -> BS.ByteString
534 signatureCommitments (ZKP voterZKP) commitment =
535 "sig|"<>voterZKP<>"|" -- NOTE: this is actually part of the statement
536 <> bytesNat commitment<>"|"
538 -- ** Type 'ErrorBallot'
539 -- | Error raised by 'encryptBallot'.
541 = ErrorBallot_WrongNumberOfAnswers Natural Natural
542 -- ^ When the number of answers
543 -- is different than the number of questions.
544 | ErrorBallot_Answer ErrorAnswer
545 -- ^ When 'encryptAnswer' raised an 'ErrorAnswer'.
546 deriving (Eq,Show,Generic,NFData)
548 -- * Type 'DecryptionShare'
549 -- | A decryption share. It is computed by a trustee from his/her
550 -- private key share and the encrypted tally,
551 -- and contains a cryptographic 'Proof' that it didn't cheat.
552 data DecryptionShare q = DecryptionShare
553 { decryptionShare_factors :: [[DecryptionFactor q]]
554 -- ^ 'DecryptionFactor' by voter, by 'Question'.
555 , decryptionShare_proofs :: [[Proof q]]
556 -- ^ 'Proof's that 'decryptionShare_factors' were correctly computed.
557 } deriving (Eq,Show,Generic,NFData)
559 -- BELENIOS: compute_factor
560 -- @('proveDecryptionShare' trusteeSecKey encByQuestByBallot)@
561 proveDecryptionShare ::
562 Monad m => SubGroup q => RandomGen r =>
563 SecretKey q -> [[Encryption q]] -> S.StateT r m (DecryptionShare q)
564 proveDecryptionShare secKey encs = do
565 res <- (proveDecryptionFactor secKey `mapM`) `mapM` encs
566 return $ uncurry DecryptionShare $ List.unzip (List.unzip <$> res)
568 -- BELENIOS: eg_factor
569 proveDecryptionFactor ::
570 Monad m => SubGroup q => RandomGen r =>
571 SecretKey q -> Encryption q -> S.StateT r m (DecryptionFactor q, Proof q)
572 proveDecryptionFactor secKey Encryption{..} = do
573 proof <- prove secKey [groupGen, encryption_nonce] (hash zkp)
574 return (encryption_nonce^secKey, proof)
575 where zkp = decryptionShareStatement (publicKey secKey)
577 decryptionShareStatement :: SubGroup q => PublicKey q -> BS.ByteString
578 decryptionShareStatement pubKey =
579 "decrypt|"<>bytesNat pubKey<>"|"
581 -- ** Type 'DecryptionFactor'
582 type DecryptionFactor = G
584 -- ** Type 'ErrorDecryptionShare'
585 data ErrorDecryptionShare
586 = ErrorDecryptionShare_Invalid
587 -- ^ The number of 'DecryptionFactor's or
588 -- the number of 'Proof's is not the same
589 -- or not the expected number.
590 | ErrorDecryptionShare_Wrong
591 -- ^ The 'Proof' of a 'DecryptionFactor' is wrong.
592 deriving (Eq,Show,Generic,NFData)
594 -- BELENIOS: check_factor
595 -- | @('verifyDecryptionShare' encByQuestByBallot pubKey decShare)@
596 -- checks that 'decShare'
597 -- (supposedly submitted by a trustee whose public key is 'pubKey')
598 -- is valid with respect to the encrypted tally 'encByQuestByBallot'.
599 verifyDecryptionShare ::
600 Monad m => SubGroup q =>
602 PublicKey q -> DecryptionShare q -> ExceptT ErrorDecryptionShare m ()
603 verifyDecryptionShare encByQuestByBallot pubKey DecryptionShare{..} =
604 let zkp = decryptionShareStatement pubKey in
605 isoZipWith3M_ (throwE ErrorDecryptionShare_Invalid)
606 (isoZipWith3M_ (throwE ErrorDecryptionShare_Invalid) $
607 \Encryption{..} decFactor proof ->
608 unless (proof_challenge proof == hash zkp
609 [ commit proof groupGen pubKey
610 , commit proof encryption_nonce decFactor
612 throwE ErrorDecryptionShare_Wrong)
614 decryptionShare_factors
615 decryptionShare_proofs
619 { tally_numBallots :: Natural
620 , tally_encByQuestByBallot :: [[Encryption q]]
621 -- ^ 'Encryption' by 'Question' by 'Ballot'.
622 , tally_decShareByTrustee :: [DecryptionShare q]
623 -- ^ 'DecryptionShare' by trustee.
624 , tally_countByQuestByBallot :: [[Natural]]
625 } deriving (Eq,Show,Generic,NFData)
627 type DecryptionShareCombinator q =
628 [DecryptionShare q] -> Except ErrorDecryptionShare [[DecryptionFactor q]]
630 -- BELENIOS: compute_result
632 Monad m => SubGroup q =>
633 [[Encryption q]] -> [DecryptionShare q] ->
634 DecryptionShareCombinator q ->
635 Except ErrorDecryptionShare (Tally q)
636 proveTally tally_encByQuestByBallot tally_decShareByTrustee decShareCombinator = do
637 decFactorByQuestByBallot <- decShareCombinator tally_decShareByTrustee
638 dec <- isoZipWithM err
639 (\encByQuest decFactorByQuest ->
641 isoZipWith (\Encryption{..} decFactor -> encryption_vault / decFactor)
645 tally_encByQuestByBallot
646 decFactorByQuestByBallot
647 let tally_numBallots = fromIntegral $ List.length tally_encByQuestByBallot
648 let logMap = Map.fromDistinctAscList $ List.zip groupGenPowers [0..tally_numBallots]
649 let log x = maybe err return $ Map.lookup x logMap
650 tally_countByQuestByBallot <- (log `mapM`)`mapM`dec
652 where err = throwE ErrorDecryptionShare_Invalid
655 Monad m => SubGroup q =>
656 DecryptionShareCombinator q -> Tally q ->
657 Except ErrorDecryptionShare ()
658 verifyTally decShareCombinator Tally{..} = do
659 decFactorByQuestByBallot <- decShareCombinator tally_decShareByTrustee
660 isoZipWith3M_ (throwE ErrorDecryptionShare_Invalid)
661 (isoZipWith3M_ (throwE ErrorDecryptionShare_Invalid)
662 (\Encryption{..} decFactor count -> do
663 let dec = encryption_vault / decFactor
664 unless (dec == groupGen ^ fromNatural count) $
665 throwE ErrorDecryptionShare_Wrong
668 tally_encByQuestByBallot
669 decFactorByQuestByBallot
670 tally_countByQuestByBallot