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.Tuple (fst, snd)
18 import GHC.Generics (Generic)
19 import Numeric.Natural (Natural)
20 import Text.Show (Show(..))
21 import qualified Data.Aeson as JSON
22 import qualified Data.Aeson.Types as JSON
23 import qualified Data.Aeson.Encoding as JSON
24 import qualified Control.Monad.Trans.State.Strict as S
25 import qualified Data.ByteString as BS
26 import qualified Data.List as List
27 import qualified Data.Map.Strict as Map
29 import Voting.Protocol.Utils
30 import Voting.Protocol.FFC
31 import Voting.Protocol.Credential
32 import Voting.Protocol.Election
36 { tally_countMax :: !Natural
37 -- ^ The maximal number of supportive 'Opinion's that a choice can get,
38 -- which is here the same as the number of 'Ballot's.
40 -- Used in 'proveTally' to decrypt the actual
41 -- count of votes obtained by a choice,
42 -- by precomputing all powers of 'groupGen's up to it.
43 , tally_encByChoiceByQuest :: !(EncryptedTally c)
44 -- ^ 'Encryption' by 'Question' by 'Ballot'.
45 , tally_decShareByTrustee :: ![DecryptionShare c]
46 -- ^ 'DecryptionShare' by trustee.
47 , tally_countByChoiceByQuest :: ![[Natural]]
48 -- ^ The decrypted count of supportive 'Opinion's, by choice by 'Question'.
49 } deriving (Eq,Show,Generic,NFData)
50 instance Reifies c FFC => ToJSON (Tally c) where
53 [ "num_tallied" .= tally_countMax
54 , "encrypted_tally" .= tally_encByChoiceByQuest
55 , "partial_decryptions" .= tally_decShareByTrustee
56 , "result" .= tally_countByChoiceByQuest
58 toEncoding Tally{..} =
60 ( "num_tallied" .= tally_countMax
61 <> "encrypted_tally" .= tally_encByChoiceByQuest
62 <> "partial_decryptions" .= tally_decShareByTrustee
63 <> "result" .= tally_countByChoiceByQuest
65 instance Reifies c FFC => FromJSON (Tally c) where
66 parseJSON = JSON.withObject "Tally" $ \o -> do
67 tally_countMax <- o .: "num_tallied"
68 tally_encByChoiceByQuest <- o .: "encrypted_tally"
69 tally_decShareByTrustee <- o .: "partial_decryptions"
70 tally_countByChoiceByQuest <- o .: "result"
73 -- ** Type 'EncryptedTally'
74 -- | 'Encryption' by choice by 'Question'.
75 type EncryptedTally c = [[Encryption c]]
77 -- | @('encryptedTally' ballots)@
78 -- returns the sum of the 'Encryption's of the given @ballots@,
79 -- along with the number of 'Ballot's.
80 encryptedTally :: Reifies c FFC => [Ballot c] -> (EncryptedTally c, Natural)
81 encryptedTally = List.foldr insertEncryptedTally emptyEncryptedTally
83 -- | The initial 'EncryptedTally' which tallies no 'Ballot'.
84 emptyEncryptedTally :: Reifies c FFC => (EncryptedTally c, Natural)
85 emptyEncryptedTally = (List.repeat (List.repeat zero), 0)
87 -- | @('insertEncryptedTally' ballot encTally)@
88 -- returns the 'EncryptedTally' adding the votes of the given @(ballot)@
89 -- to those of the given @(encTally)@.
90 insertEncryptedTally :: Reifies c FFC => Ballot c -> (EncryptedTally c, Natural) -> (EncryptedTally c, Natural)
91 insertEncryptedTally Ballot{..} (encTally, numBallots) =
93 (\Answer{..} -> List.zipWith (+) (fst <$> answer_opinions))
99 -- ** Type 'DecryptionShareCombinator'
100 type DecryptionShareCombinator c =
101 EncryptedTally c -> [DecryptionShare c] -> Except ErrorTally [[DecryptionFactor c]]
105 (EncryptedTally c, Natural) -> [DecryptionShare c] ->
106 DecryptionShareCombinator c ->
107 Except ErrorTally (Tally c)
109 (tally_encByChoiceByQuest, tally_countMax)
110 tally_decShareByTrustee
111 decShareCombinator = do
112 decFactorByChoiceByQuest <-
114 tally_encByChoiceByQuest
115 tally_decShareByTrustee
116 dec <- isoZipWithM (throwE ErrorTally_NumberOfQuestions)
117 (maybe (throwE ErrorTally_NumberOfChoices) return `o2`
118 isoZipWith (\Encryption{..} decFactor -> encryption_vault / decFactor))
119 tally_encByChoiceByQuest
120 decFactorByChoiceByQuest
121 let logMap = Map.fromList $ List.zip groupGenPowers [0..tally_countMax]
123 maybe (throwE ErrorTally_CannotDecryptCount) return $
125 tally_countByChoiceByQuest <- (log `mapM`)`mapM`dec
130 Tally c -> DecryptionShareCombinator c ->
132 verifyTally Tally{..} decShareCombinator = do
133 decFactorByChoiceByQuest <- decShareCombinator tally_encByChoiceByQuest tally_decShareByTrustee
134 isoZipWith3M_ (throwE ErrorTally_NumberOfQuestions)
135 (isoZipWith3M_ (throwE ErrorTally_NumberOfChoices)
136 (\Encryption{..} decFactor count -> do
137 let groupGenPowCount = encryption_vault / decFactor
138 unless (groupGenPowCount == groupGen ^ fromNatural count) $
139 throwE ErrorTally_WrongProof))
140 tally_encByChoiceByQuest
141 decFactorByChoiceByQuest
142 tally_countByChoiceByQuest
144 -- ** Type 'DecryptionShare'
145 -- | A decryption share is a 'DecryptionFactor' and a decryption 'Proof', by choice by 'Question'.
146 -- Computed by a trustee in 'proveDecryptionShare'.
147 newtype DecryptionShare c = DecryptionShare
148 { unDecryptionShare :: [[(DecryptionFactor c, Proof c)]] }
149 deriving (Eq,Show,Generic)
150 deriving newtype instance NFData (DecryptionShare c)
151 instance ToJSON (DecryptionShare c) where
152 toJSON (DecryptionShare decByChoiceByQuest) =
154 [ "decryption_factors" .=
155 toJSONList (((toJSON . fst) <$>) <$> decByChoiceByQuest)
156 , "decryption_proofs" .=
157 toJSONList (((toJSON . snd) <$>) <$> decByChoiceByQuest)
159 toEncoding (DecryptionShare decByChoiceByQuest) =
161 JSON.pair "decryption_factors"
162 (JSON.list (JSON.list (toEncoding . fst)) decByChoiceByQuest) <>
163 JSON.pair "decryption_proofs"
164 (JSON.list (JSON.list (toEncoding . snd)) decByChoiceByQuest)
165 instance Reifies c FFC => FromJSON (DecryptionShare c) where
166 parseJSON = JSON.withObject "DecryptionShare" $ \o -> do
167 decFactors <- o .: "decryption_factors"
168 decProofs <- o .: "decryption_proofs"
169 let err msg = JSON.typeMismatch ("DecryptionShare: "<>msg) (JSON.Object o)
171 <$> isoZipWithM (err "inconsistent number of questions")
172 (isoZipWithM (err "inconsistent number of choices")
173 (\a b -> return (a, b)))
176 -- *** Type 'DecryptionFactor'
177 -- | @'encryption_nonce' '^'trusteeSecKey@
178 type DecryptionFactor = G
180 -- @('proveDecryptionShare' encByChoiceByQuest trusteeSecKey)@
181 proveDecryptionShare ::
182 Monad m => Reifies c FFC => RandomGen r =>
183 EncryptedTally c -> SecretKey c -> S.StateT r m (DecryptionShare c)
184 proveDecryptionShare encByChoiceByQuest trusteeSecKey =
185 (DecryptionShare <$>) $
186 (proveDecryptionFactor trusteeSecKey `mapM`) `mapM` encByChoiceByQuest
188 proveDecryptionFactor ::
189 Monad m => Reifies c FFC => RandomGen r =>
190 SecretKey c -> Encryption c -> S.StateT r m (DecryptionFactor c, Proof c)
191 proveDecryptionFactor trusteeSecKey Encryption{..} = do
192 proof <- prove trusteeSecKey [groupGen, encryption_nonce] (hash zkp)
193 return (encryption_nonce^trusteeSecKey, proof)
194 where zkp = decryptionShareStatement (publicKey trusteeSecKey)
196 decryptionShareStatement :: Reifies c FFC => PublicKey c -> BS.ByteString
197 decryptionShareStatement pubKey =
198 "decrypt|"<>bytesNat pubKey<>"|"
200 -- *** Type 'ErrorTally'
202 = ErrorTally_NumberOfQuestions
203 -- ^ The number of 'Question's is not the one expected.
204 | ErrorTally_NumberOfChoices
205 -- ^ The number of choices is not the one expected.
206 | ErrorTally_NumberOfTrustees
207 -- ^ The number of trustees is not the one expected.
208 | ErrorTally_WrongProof
209 -- ^ The 'Proof' of a 'DecryptionFactor' is wrong.
210 | ErrorTally_CannotDecryptCount
211 -- ^ Raised by 'proveTally' when the discrete logarithm of @'groupGen' '^'count@
212 -- cannot be computed, likely because 'tally_countMax' is wrong,
213 -- or because the 'EncryptedTally' or 'DecryptionShare's have not been verified.
214 deriving (Eq,Show,Generic,NFData)
216 -- | @('verifyDecryptionShare' encTally trusteePubKey trusteeDecShare)@
217 -- checks that 'trusteeDecShare'
218 -- (supposedly submitted by a trustee whose 'PublicKey' is 'trusteePubKey')
219 -- is valid with respect to the 'EncryptedTally' 'encTally'.
220 verifyDecryptionShare ::
221 Monad m => Reifies c FFC =>
222 EncryptedTally c -> PublicKey c -> DecryptionShare c ->
223 ExceptT ErrorTally m ()
224 verifyDecryptionShare encByChoiceByQuest trusteePubKey (DecryptionShare decShare) =
225 let zkp = decryptionShareStatement trusteePubKey in
226 isoZipWithM_ (throwE ErrorTally_NumberOfQuestions)
227 (isoZipWithM_ (throwE ErrorTally_NumberOfChoices) $
228 \Encryption{..} (decFactor, proof) ->
229 unless (proof_challenge proof == hash zkp
230 [ commit proof groupGen trusteePubKey
231 , commit proof encryption_nonce decFactor
232 ]) $ throwE ErrorTally_WrongProof)
236 verifyDecryptionShareByTrustee ::
237 Monad m => Reifies c FFC =>
238 EncryptedTally c -> [PublicKey c] -> [DecryptionShare c] ->
239 ExceptT ErrorTally m ()
240 verifyDecryptionShareByTrustee encTally =
241 isoZipWithM_ (throwE ErrorTally_NumberOfTrustees)
242 (verifyDecryptionShare encTally)