1 {-# LANGUAGE DeriveAnyClass #-}
2 {-# LANGUAGE DeriveGeneric #-}
3 {-# LANGUAGE DerivingStrategies #-}
4 {-# LANGUAGE OverloadedStrings #-}
5 {-# LANGUAGE UndecidableInstances #-} -- for Reifies instances
6 module Voting.Protocol.Tally where
8 import Control.DeepSeq (NFData)
9 import Control.Monad (Monad(..), mapM, unless)
10 import Control.Monad.Trans.Except (Except, ExceptT, throwE)
11 import Data.Aeson (ToJSON(..),FromJSON(..),(.:),(.=))
12 import Data.Eq (Eq(..))
13 import Data.Function (($), (.))
14 import Data.Functor ((<$>))
15 import Data.Maybe (maybe)
16 import Data.Semigroup (Semigroup(..))
17 import Data.Ord (Ord(..))
18 import Data.Reflection (Reifies(..))
19 import Data.Tuple (fst, snd)
20 import GHC.Generics (Generic)
21 import Numeric.Natural (Natural)
22 import System.Random (RandomGen)
23 import Text.Show (Show(..))
24 import qualified Data.Aeson as JSON
25 import qualified Data.Aeson.Types as JSON
26 import qualified Data.Aeson.Encoding as JSON
27 import qualified Control.Monad.Trans.State.Strict as S
28 import qualified Data.ByteString as BS
29 import qualified Data.List as List
30 import qualified Data.Map.Strict as Map
32 import Voting.Protocol.Utils
33 import Voting.Protocol.Arith
34 import Voting.Protocol.Credential
35 import Voting.Protocol.Election
38 data Tally crypto v c = Tally
39 { tally_countMax :: !Natural
40 -- ^ The maximal number of supportive 'Opinion's that a choice can get,
41 -- which is here the same as the number of 'Ballot's.
43 -- Used in 'proveTally' to decrypt the actual
44 -- count of votes obtained by a choice,
45 -- by precomputing all powers of 'groupGen's up to it.
46 , tally_encByChoiceByQuest :: !(EncryptedTally crypto v c)
47 -- ^ 'Encryption' by 'Question' by 'Ballot'.
48 , tally_decShareByTrustee :: ![DecryptionShare crypto v c]
49 -- ^ 'DecryptionShare' by trustee.
50 , tally_countByChoiceByQuest :: ![[Natural]]
51 -- ^ The decrypted count of supportive 'Opinion's, by choice by 'Question'.
53 deriving instance Eq (G crypto c) => Eq (Tally crypto v c)
54 deriving instance (Show (G crypto c), Show (G crypto c)) => Show (Tally crypto v c)
55 deriving instance NFData (G crypto c) => NFData (Tally crypto v c)
61 ) => ToJSON (Tally crypto v c) where
64 [ "num_tallied" .= tally_countMax
65 , "encrypted_tally" .= tally_encByChoiceByQuest
66 , "partial_decryptions" .= tally_decShareByTrustee
67 , "result" .= tally_countByChoiceByQuest
69 toEncoding Tally{..} =
71 ( "num_tallied" .= tally_countMax
72 <> "encrypted_tally" .= tally_encByChoiceByQuest
73 <> "partial_decryptions" .= tally_decShareByTrustee
74 <> "result" .= tally_countByChoiceByQuest
80 , FromJSON (G crypto c)
81 ) => FromJSON (Tally crypto v c) where
82 parseJSON = JSON.withObject "Tally" $ \o -> do
83 tally_countMax <- o .: "num_tallied"
84 tally_encByChoiceByQuest <- o .: "encrypted_tally"
85 tally_decShareByTrustee <- o .: "partial_decryptions"
86 tally_countByChoiceByQuest <- o .: "result"
89 -- ** Type 'EncryptedTally'
90 -- | 'Encryption' by choice by 'Question'.
91 type EncryptedTally crypto v c = [[Encryption crypto v c]]
93 -- | @('encryptedTally' ballots)@
94 -- returns the sum of the 'Encryption's of the given @ballots@,
95 -- along with the number of 'Ballot's.
98 Multiplicative (G crypto c) =>
99 Invertible (G crypto c) =>
100 [Ballot crypto v c] -> (EncryptedTally crypto v c, Natural)
101 encryptedTally = List.foldr insertEncryptedTally emptyEncryptedTally
103 -- | The initial 'EncryptedTally' which tallies no 'Ballot'.
104 emptyEncryptedTally ::
106 Multiplicative (G crypto c) =>
107 Invertible (G crypto c) =>
108 (EncryptedTally crypto v c, Natural)
109 emptyEncryptedTally = (List.repeat (List.repeat zero), 0)
111 -- | @('insertEncryptedTally' ballot encTally)@
112 -- returns the 'EncryptedTally' adding the votes of the given @(ballot)@
113 -- to those of the given @(encTally)@.
114 insertEncryptedTally ::
116 Multiplicative (G crypto c) =>
117 Invertible (G crypto c) =>
118 Ballot crypto v c -> (EncryptedTally crypto v c, Natural) -> (EncryptedTally crypto v c, Natural)
119 insertEncryptedTally Ballot{..} (encTally, numBallots) =
121 (\Answer{..} -> List.zipWith (+) (fst <$> answer_opinions))
127 -- ** Type 'DecryptionShareCombinator'
128 type DecryptionShareCombinator crypto v c =
129 EncryptedTally crypto v c ->
130 [DecryptionShare crypto v c] ->
131 Except ErrorTally [[DecryptionFactor crypto c]]
136 Multiplicative (G crypto c) =>
137 Invertible (G crypto c) =>
139 (EncryptedTally crypto v c, Natural) -> [DecryptionShare crypto v c] ->
140 DecryptionShareCombinator crypto v c ->
141 Except ErrorTally (Tally crypto v c)
143 (tally_encByChoiceByQuest, tally_countMax)
144 tally_decShareByTrustee
145 decShareCombinator = do
146 decFactorByChoiceByQuest <-
148 tally_encByChoiceByQuest
149 tally_decShareByTrustee
150 dec <- isoZipWithM (throwE ErrorTally_NumberOfQuestions)
151 (maybe (throwE ErrorTally_NumberOfChoices) return `o2`
152 isoZipWith (\Encryption{..} decFactor -> encryption_vault / decFactor))
153 tally_encByChoiceByQuest
154 decFactorByChoiceByQuest
155 let logMap = Map.fromList $ List.zip groupGenPowers [0..tally_countMax]
157 maybe (throwE ErrorTally_CannotDecryptCount) return $
159 tally_countByChoiceByQuest <- (log `mapM`)`mapM`dec
165 Multiplicative (G crypto c) =>
166 Invertible (G crypto c) =>
169 DecryptionShareCombinator crypto v c ->
171 verifyTally Tally{..} decShareCombinator = do
172 decFactorByChoiceByQuest <- decShareCombinator tally_encByChoiceByQuest tally_decShareByTrustee
173 isoZipWith3M_ (throwE ErrorTally_NumberOfQuestions)
174 (isoZipWith3M_ (throwE ErrorTally_NumberOfChoices)
175 (\Encryption{..} decFactor count -> do
176 let groupGenPowCount = encryption_vault / decFactor
177 unless (groupGenPowCount == groupGen ^ fromNatural count) $
178 throwE ErrorTally_WrongProof))
179 tally_encByChoiceByQuest
180 decFactorByChoiceByQuest
181 tally_countByChoiceByQuest
183 -- ** Type 'DecryptionShare'
184 -- | A decryption share is a 'DecryptionFactor' and a decryption 'Proof', by choice by 'Question'.
185 -- Computed by a trustee in 'proveDecryptionShare'.
186 newtype DecryptionShare crypto v c = DecryptionShare
187 { unDecryptionShare :: [[(DecryptionFactor crypto c, Proof crypto v c)]] }
189 deriving instance Eq (G crypto c) => Eq (DecryptionShare crypto v c)
190 deriving instance Show (G crypto c) => Show (DecryptionShare crypto v c)
191 deriving newtype instance NFData (G crypto c) => NFData (DecryptionShare crypto v c)
194 , ToJSON (G crypto c)
195 ) => ToJSON (DecryptionShare crypto v c) where
196 toJSON (DecryptionShare decByChoiceByQuest) =
198 [ "decryption_factors" .=
199 toJSONList (((toJSON . fst) <$>) <$> decByChoiceByQuest)
200 , "decryption_proofs" .=
201 toJSONList (((toJSON . snd) <$>) <$> decByChoiceByQuest)
203 toEncoding (DecryptionShare decByChoiceByQuest) =
205 JSON.pair "decryption_factors"
206 (JSON.list (JSON.list (toEncoding . fst)) decByChoiceByQuest) <>
207 JSON.pair "decryption_proofs"
208 (JSON.list (JSON.list (toEncoding . snd)) decByChoiceByQuest)
212 , FromJSON (G crypto c)
213 ) => FromJSON (DecryptionShare crypto v c) where
214 parseJSON = JSON.withObject "DecryptionShare" $ \o -> do
215 decFactors <- o .: "decryption_factors"
216 decProofs <- o .: "decryption_proofs"
217 let err msg = JSON.typeMismatch ("DecryptionShare: "<>msg) (JSON.Object o)
219 <$> isoZipWithM (err "inconsistent number of questions")
220 (isoZipWithM (err "inconsistent number of choices")
221 (\a b -> return (a, b)))
224 -- *** Type 'DecryptionFactor'
225 -- | @'encryption_nonce' '^'trusteeSecKey@
226 type DecryptionFactor = G
228 -- @('proveDecryptionShare' encByChoiceByQuest trusteeSecKey)@
229 proveDecryptionShare ::
233 Multiplicative (G crypto c) =>
234 Invertible (G crypto c) =>
236 ToNatural (G crypto c) =>
237 Monad m => RandomGen r =>
238 EncryptedTally crypto v c -> SecretKey crypto c -> S.StateT r m (DecryptionShare crypto v c)
239 proveDecryptionShare encByChoiceByQuest trusteeSecKey =
240 (DecryptionShare <$>) $
241 (proveDecryptionFactor trusteeSecKey `mapM`) `mapM` encByChoiceByQuest
243 proveDecryptionFactor ::
247 Multiplicative (G crypto c) =>
248 Invertible (G crypto c) =>
250 ToNatural (G crypto c) =>
251 Monad m => RandomGen r =>
252 SecretKey crypto c -> Encryption crypto v c -> S.StateT r m (DecryptionFactor crypto c, Proof crypto v c)
253 proveDecryptionFactor trusteeSecKey Encryption{..} = do
254 proof <- prove trusteeSecKey [groupGen, encryption_nonce] (hash zkp)
255 return (encryption_nonce^trusteeSecKey, proof)
256 where zkp = decryptionShareStatement (publicKey trusteeSecKey)
258 decryptionShareStatement ::
260 ToNatural (G crypto c) =>
261 PublicKey crypto c -> BS.ByteString
262 decryptionShareStatement pubKey =
263 "decrypt|"<>bytesNat pubKey<>"|"
265 -- *** Type 'ErrorTally'
267 = ErrorTally_NumberOfQuestions
268 -- ^ The number of 'Question's is not the one expected.
269 | ErrorTally_NumberOfChoices
270 -- ^ The number of choices is not the one expected.
271 | ErrorTally_NumberOfTrustees
272 -- ^ The number of trustees is not the one expected.
273 | ErrorTally_WrongProof
274 -- ^ The 'Proof' of a 'DecryptionFactor' is wrong.
275 | ErrorTally_CannotDecryptCount
276 -- ^ Raised by 'proveTally' when the discrete logarithm of @'groupGen' '^'count@
277 -- cannot be computed, likely because 'tally_countMax' is wrong,
278 -- or because the 'EncryptedTally' or 'DecryptionShare's have not been verified.
279 deriving (Eq,Show,Generic,NFData)
281 -- | @('verifyDecryptionShare' encTally trusteePubKey trusteeDecShare)@
282 -- checks that 'trusteeDecShare'
283 -- (supposedly submitted by a trustee whose 'PublicKey' is 'trusteePubKey')
284 -- is valid with respect to the 'EncryptedTally' 'encTally'.
285 verifyDecryptionShare ::
289 Multiplicative (G crypto c) =>
290 Invertible (G crypto c) =>
291 ToNatural (G crypto c) =>
293 EncryptedTally crypto v c -> PublicKey crypto c -> DecryptionShare crypto v c ->
294 ExceptT ErrorTally m ()
295 verifyDecryptionShare encByChoiceByQuest trusteePubKey (DecryptionShare decShare) =
296 let zkp = decryptionShareStatement trusteePubKey in
297 isoZipWithM_ (throwE ErrorTally_NumberOfQuestions)
298 (isoZipWithM_ (throwE ErrorTally_NumberOfChoices) $
299 \Encryption{..} (decFactor, proof) ->
300 unless (proof_challenge proof == hash zkp
301 [ commit proof groupGen trusteePubKey
302 , commit proof encryption_nonce decFactor
303 ]) $ throwE ErrorTally_WrongProof)
307 verifyDecryptionShareByTrustee ::
311 Multiplicative (G crypto c) =>
312 Invertible (G crypto c) =>
313 ToNatural (G crypto c) =>
315 EncryptedTally crypto v c -> [PublicKey crypto c] -> [DecryptionShare crypto v c] ->
316 ExceptT ErrorTally m ()
317 verifyDecryptionShareByTrustee encTally =
318 isoZipWithM_ (throwE ErrorTally_NumberOfTrustees)
319 (verifyDecryptionShare encTally)