1 {-# LANGUAGE DeriveAnyClass #-}
2 {-# LANGUAGE DeriveGeneric #-}
3 {-# LANGUAGE OverloadedStrings #-}
4 module Voting.Protocol.Tally where
6 import Control.DeepSeq (NFData)
7 import Control.Monad (Monad(..), mapM, unless)
8 import Control.Monad.Trans.Except (Except, ExceptT, throwE)
9 import Data.Eq (Eq(..))
10 import Data.Function (($))
11 import Data.Functor ((<$>))
12 import Data.Maybe (maybe)
13 import Data.Semigroup (Semigroup(..))
14 import Data.Tuple (fst)
15 import GHC.Generics (Generic)
16 import Numeric.Natural (Natural)
17 import Prelude (fromIntegral)
18 import Text.Show (Show(..))
19 import qualified Control.Monad.Trans.State.Strict as S
20 import qualified Data.ByteString as BS
21 import qualified Data.List as List
22 import qualified Data.Map.Strict as Map
24 import Voting.Protocol.Utils
25 import Voting.Protocol.Arithmetic
26 import Voting.Protocol.Credential
27 import Voting.Protocol.Election
31 { tally_countMax :: Natural
32 -- ^ The maximal number of supportive 'Opinion's that a choice can get,
33 -- which is here the same as the number of 'Ballot's.
35 -- Used in 'proveTally' to decrypt the actual
36 -- count of votes obtained by a choice,
37 -- by precomputing all powers of 'groupGen's up to it.
38 , tally_encByChoiceByQuest :: EncryptedTally q
39 -- ^ 'Encryption' by 'Question' by 'Ballot'.
40 , tally_decShareByTrustee :: [DecryptionShare q]
41 -- ^ 'DecryptionShare' by trustee.
42 , tally_countByChoiceByQuest :: [[Natural]]
43 -- ^ The decrypted count of supportive 'Opinion's, by choice by 'Question'.
44 } deriving (Eq,Show,Generic,NFData)
46 -- ** Type 'EncryptedTally'
47 -- | 'Encryption' by choice by 'Question'.
48 type EncryptedTally q = [[Encryption q]]
50 -- | @('encryptedTally' ballots)@
51 -- returns the sum of the 'Encryption's of the given @ballots@,
52 -- along with the number of 'Ballot's.
53 encryptedTally :: SubGroup q => [Ballot q] -> (EncryptedTally q, Natural)
54 encryptedTally ballots =
55 ( List.foldr (\Ballot{..} ->
56 List.zipWith (\Answer{..} ->
58 (fst <$> answer_opinions))
60 (List.repeat (List.repeat zero))
62 , fromIntegral $ List.length ballots
65 -- ** Type 'DecryptionShareCombinator'
66 type DecryptionShareCombinator q =
67 EncryptedTally q -> [DecryptionShare q] -> Except ErrorTally [[DecryptionFactor q]]
71 (EncryptedTally q, Natural) -> [DecryptionShare q] ->
72 DecryptionShareCombinator q ->
73 Except ErrorTally (Tally q)
75 (tally_encByChoiceByQuest, tally_countMax)
76 tally_decShareByTrustee
77 decShareCombinator = do
78 decFactorByChoiceByQuest <- decShareCombinator tally_encByChoiceByQuest tally_decShareByTrustee
79 dec <- isoZipWithM (throwE ErrorTally_NumberOfQuestions)
80 (maybe (throwE ErrorTally_NumberOfChoices) return `o2`
81 isoZipWith (\Encryption{..} decFactor -> encryption_vault / decFactor))
82 tally_encByChoiceByQuest
83 decFactorByChoiceByQuest
84 let logMap = Map.fromList $ List.zip groupGenPowers [0..tally_countMax]
86 maybe (throwE ErrorTally_CannotDecryptCount) return $
88 tally_countByChoiceByQuest <- (log `mapM`)`mapM`dec
93 Tally q -> DecryptionShareCombinator q ->
95 verifyTally Tally{..} decShareCombinator = do
96 decFactorByChoiceByQuest <- decShareCombinator tally_encByChoiceByQuest tally_decShareByTrustee
97 isoZipWith3M_ (throwE ErrorTally_NumberOfQuestions)
98 (isoZipWith3M_ (throwE ErrorTally_NumberOfChoices)
99 (\Encryption{..} decFactor count -> do
100 let groupGenPowCount = encryption_vault / decFactor
101 unless (groupGenPowCount == groupGen ^ fromNatural count) $
102 throwE ErrorTally_WrongProof))
103 tally_encByChoiceByQuest
104 decFactorByChoiceByQuest
105 tally_countByChoiceByQuest
107 -- ** Type 'DecryptionShare'
108 -- | A decryption share is a 'DecryptionFactor' and a decryption 'Proof', by choice by 'Question'.
109 -- Computed by a trustee in 'proveDecryptionShare'.
110 type DecryptionShare q = [[(DecryptionFactor q, Proof q)]]
112 -- *** Type 'DecryptionFactor'
113 -- | @'encryption_nonce' '^'trusteeSecKey@
114 type DecryptionFactor = G
116 -- @('proveDecryptionShare' encByChoiceByQuest trusteeSecKey)@
117 proveDecryptionShare ::
118 Monad m => SubGroup q => RandomGen r =>
119 EncryptedTally q -> SecretKey q -> S.StateT r m (DecryptionShare q)
120 proveDecryptionShare encByChoiceByQuest trusteeSecKey =
121 (proveDecryptionFactor trusteeSecKey `mapM`) `mapM` encByChoiceByQuest
123 proveDecryptionFactor ::
124 Monad m => SubGroup q => RandomGen r =>
125 SecretKey q -> Encryption q -> S.StateT r m (DecryptionFactor q, Proof q)
126 proveDecryptionFactor trusteeSecKey Encryption{..} = do
127 proof <- prove trusteeSecKey [groupGen, encryption_nonce] (hash zkp)
128 return (encryption_nonce^trusteeSecKey, proof)
129 where zkp = decryptionShareStatement (publicKey trusteeSecKey)
131 decryptionShareStatement :: SubGroup q => PublicKey q -> BS.ByteString
132 decryptionShareStatement pubKey =
133 "decrypt|"<>bytesNat pubKey<>"|"
135 -- *** Type 'ErrorTally'
137 = ErrorTally_NumberOfQuestions
138 -- ^ The number of 'Question's is not the one expected.
139 | ErrorTally_NumberOfChoices
140 -- ^ The number of choices is not the one expected.
141 | ErrorTally_NumberOfTrustees
142 -- ^ The number of trustees is not the one expected.
143 | ErrorTally_WrongProof
144 -- ^ The 'Proof' of a 'DecryptionFactor' is wrong.
145 | ErrorTally_CannotDecryptCount
146 -- ^ Raised by 'proveTally' when the discrete logarithm of @'groupGen' '^'count@
147 -- cannot be computed, likely because 'tally_countMax' is wrong,
148 -- or because the 'EncryptedTally' or 'DecryptionShare's have not been verified.
149 deriving (Eq,Show,Generic,NFData)
151 -- | @('verifyDecryptionShare' encTally trusteePubKey trusteeDecShare)@
152 -- checks that 'trusteeDecShare'
153 -- (supposedly submitted by a trustee whose 'PublicKey' is 'trusteePubKey')
154 -- is valid with respect to the 'EncryptedTally' 'encTally'.
155 verifyDecryptionShare ::
156 Monad m => SubGroup q =>
157 EncryptedTally q -> PublicKey q -> DecryptionShare q ->
158 ExceptT ErrorTally m ()
159 verifyDecryptionShare encByChoiceByQuest trusteePubKey =
160 let zkp = decryptionShareStatement trusteePubKey in
161 isoZipWithM_ (throwE ErrorTally_NumberOfQuestions)
162 (isoZipWithM_ (throwE ErrorTally_NumberOfChoices) $
163 \Encryption{..} (decFactor, proof) ->
164 unless (proof_challenge proof == hash zkp
165 [ commit proof groupGen trusteePubKey
166 , commit proof encryption_nonce decFactor
167 ]) $ throwE ErrorTally_WrongProof)
170 verifyDecryptionShareByTrustee ::
171 Monad m => SubGroup q =>
172 EncryptedTally q -> [PublicKey q] -> [DecryptionShare q] ->
173 ExceptT ErrorTally m ()
174 verifyDecryptionShareByTrustee encTally =
175 isoZipWithM_ (throwE ErrorTally_NumberOfTrustees)
176 (verifyDecryptionShare encTally)