1 {-# LANGUAGE OverloadedStrings #-}
2 {-# LANGUAGE UndecidableInstances #-} -- for Reifies instances
3 {-# OPTIONS -fno-warn-orphans #-}
4 module QuickCheck.Election where
6 import Data.Eq (Eq(..))
8 import Data.Maybe (fromJust)
9 import Data.Ord (Ord(..))
10 import GHC.Natural (minusNaturalMaybe)
11 import Prelude (undefined)
12 import Test.Tasty.QuickCheck
13 import qualified Data.Aeson as JSON
14 import qualified Data.List as List
15 import qualified Data.Text as Text
17 import Voting.Protocol
21 -- Hardcoded limits to avoid keep a reasonable testing time.
22 maxArbitraryChoices :: Natural
23 maxArbitraryChoices = 5
24 maxArbitraryQuestions :: Natural
25 maxArbitraryQuestions = 2
27 quickcheck :: Reifies v Version => Proxy v -> TestTree
30 [ testGroup "verifyBallot" $
31 [ reify weakFFC $ quickcheckElection v
32 , reify beleniosFFC $ quickcheckElection v
37 Reifies c crypto => Group crypto => Key crypto => JSON.ToJSON crypto => Show crypto =>
38 Reifies v Version => Proxy v ->
40 quickcheckElection (_v::Proxy v) (c::Proxy c)
41 | Dict <- groupDict c =
42 testGroup (Text.unpack $ cryptoName (reflect c))
43 [ testProperty "verifyBallot" $ \(seed, (elec::Election crypto v c) :> votes) ->
45 (`evalStateT` mkStdGen seed) $ do
46 -- ballotSecKey :: SecretKey c <- randomSecretKey
47 ballot <- encryptBallot elec Nothing votes
48 unless (verifyBallot elec ballot) $
49 lift $ throwE $ ErrorBallot_Wrong
53 instance Reifies c FFC => Arbitrary (F c) where
54 arbitrary = F <$> choose (zero, fromJust $ fieldCharac @c `minusNaturalMaybe` one)
59 , Multiplicative (G crypto c)
60 , Invertible (G crypto c)
61 ) => Arbitrary (G crypto c) where
68 ) => Arbitrary (E crypto c) where
69 arbitrary = E <$> choose (zero, fromJust $ groupOrder @crypto (Proxy @c) `minusNaturalMaybe` one)
70 instance Arbitrary UUID where
73 (`evalStateT` mkStdGen seed) $
78 , Arbitrary (E crypto c)
79 ) => Arbitrary (Proof crypto v c) where
81 proof_challenge <- arbitrary
82 proof_response <- arbitrary
84 instance Reifies v Version => Arbitrary (Question v) where
86 let question_text = "question"
87 choices :: Natural <- choose (1, maxArbitraryChoices)
88 let question_choices = [Text.pack ("c"<>show c) | c <- [1..choices]]
89 question_mini <- choose (0, choices)
90 question_maxi <- choose (nat question_mini, choices)
93 [ quest{question_choices, question_mini, question_maxi}
94 | question_choices <- shrinkList pure $ question_choices quest
95 , let nChoices = fromIntegral $ List.length question_choices
96 , question_mini <- shrinkIntegral $ min nChoices $ max zero $ question_mini quest
97 , question_maxi <- shrinkIntegral $ min nChoices $ max question_mini $ question_maxi quest
104 , Multiplicative (G crypto c)
105 , Invertible (G crypto c)
107 , JSON.ToJSON (G crypto c)
108 ) => Arbitrary (Election crypto v c) where
110 let election_name = "election"
111 let election_description = "description"
112 let election_crypto = reflect (Proxy @c)
113 election_secret_key <- arbitrary
114 let election_public_key = publicKey election_secret_key
115 election_questions <- resize (fromIntegral maxArbitraryQuestions) $ listOf1 arbitrary
116 election_uuid <- arbitrary
118 { election_hash = hashElection elec
119 , election_version = Just (reflect (Proxy @v))
124 [ elec{election_questions}
125 | election_questions <- shrink $ election_questions elec
128 instance Reifies c FFC => Arbitrary (ElectionCrypto c) where
130 let electionCrypto_FFC_params = reflect (Proxy::Proxy c)
131 electionCrypto_FFC_PublicKey <- arbitrary
132 return ElectionCrypto_FFC{..}
135 -- | A type to declare an 'Arbitrary' instance where @b@ depends on @a@.
136 data (:>) a b = a :> b
138 instance Reifies v Version => Arbitrary (Question v :> [Bool]) where
140 quest@Question{..} <- arbitrary
142 let numChoices = List.length question_choices
143 numTrue <- fromIntegral <$> choose (nat question_mini, nat question_maxi)
144 rank <- choose (0, nCk numChoices numTrue - 1)
145 return $ boolsOfCombin numChoices numTrue rank
146 return (quest :> votes)
147 shrink (quest :> votes) =
148 [ q :> shrinkVotes q votes
157 ) => Arbitrary (Election crypto v c :> [[Bool]]) where
158 arbitrary | Dict <- groupDict (Proxy @c) = do
159 elec@Election{..} <- arbitrary
160 votes <- forM election_questions $ \Question{..} -> do
161 let numChoices = List.length question_choices
162 numTrue <- fromIntegral <$> choose (nat question_mini, nat question_maxi)
163 rank <- choose (0, nCk numChoices numTrue - 1)
164 return $ boolsOfCombin numChoices numTrue rank
165 return (elec :> votes)
166 shrink | Dict <- groupDict (Proxy @c) = \(elec :> votes) ->
167 [ e :> List.zipWith shrinkVotes (election_questions e :: [Question v]) votes
171 -- | @('boolsOfCombin' nBits nTrue rank)@ returns the 'rank'-th sequence of 'nBits'-bits possible
172 -- with 'nTrue' bits set at 'True' and @(nBits-nTrue)@ set at 'False'.
173 -- @rank@ has to be in @[0 .. 'nCk' nBits nTrue '-' 1]@
174 boolsOfCombin :: Int -> Int -> Int -> [Bool]
175 boolsOfCombin nBits nTrue rank
176 | rank < 0 = undefined
177 | nTrue == 0 = List.replicate nBits False
178 | otherwise = go 0 cs <> List.replicate (nBits-List.last cs) False
180 cs = combinOfRank nBits nTrue rank
183 List.replicate (next-1-curr) False <> [True]
186 -- | @('shrinkVotes' quest votes)@
187 -- returns a reduced version of the given @votes@
188 -- to fit the requirement of the given @quest@.
189 shrinkVotes :: Reifies v Version => Question v -> [Bool] -> [Bool]
190 shrinkVotes Question{..} votes =
191 (\(nTrue, b) -> nTrue <= nat question_maxi && b)
192 <$> List.zip (countTrue votes) votes
194 countTrue :: [Bool] -> [Natural]
195 countTrue = List.foldl' (\ns b -> if b then inc ns else ns) []
198 inc (n:ns) = (n+one):n:ns