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.List as List
14 import qualified Data.Text as Text
16 import Voting.Protocol
20 -- Hardcoded limits to avoid keep a reasonable testing time.
21 maxArbitraryChoices :: Natural
22 maxArbitraryChoices = 5
23 maxArbitraryQuestions :: Natural
24 maxArbitraryQuestions = 2
26 quickcheck :: TestTree
29 [ testGroup "verifyBallot" $
30 [ testElection weakFFC
31 , testElection beleniosFFC
35 testElection :: FFC -> TestTree
37 reify ffc $ \(Proxy::Proxy c) ->
38 testGroup (Text.unpack $ ffc_name ffc)
39 [ testProperty "verifyBallot" $ \(seed, (elec::Election c) :> votes) ->
41 (`evalStateT` mkStdGen seed) $ do
42 -- ballotSecKey :: SecretKey c <- randomSecretKey
43 ballot <- encryptBallot elec Nothing votes
44 unless (verifyBallot elec ballot) $
45 lift $ throwE $ ErrorBallot_Wrong
48 instance Reifies c FFC => Arbitrary (F c) where
49 arbitrary = F <$> choose (zero, fromJust $ fieldCharac @c `minusNaturalMaybe` one)
50 instance Reifies c FFC => Arbitrary (G c) where
54 instance Reifies c FFC => Arbitrary (E c) where
55 arbitrary = E <$> choose (zero, fromJust $ groupOrder @c `minusNaturalMaybe` one)
56 instance Arbitrary UUID where
59 (`evalStateT` mkStdGen seed) $
61 instance Reifies c FFC => Arbitrary (Proof c) where
63 proof_challenge <- arbitrary
64 proof_response <- arbitrary
66 instance Arbitrary Question where
68 let question_text = "question"
69 choices :: Natural <- choose (1, maxArbitraryChoices)
70 let question_choices = [Text.pack ("c"<>show c) | c <- [1..choices]]
71 question_mini <- choose (0, choices)
72 question_maxi <- choose (nat question_mini, choices)
75 [ quest{question_choices, question_mini, question_maxi}
76 | question_choices <- shrinkList pure $ question_choices quest
77 , let nChoices = fromIntegral $ List.length question_choices
78 , question_mini <- shrinkIntegral $ min nChoices $ max zero $ question_mini quest
79 , question_maxi <- shrinkIntegral $ min nChoices $ max question_mini $ question_maxi quest
81 instance Reifies c FFC => Arbitrary (Election c) where
83 let election_name = "election"
84 let election_description = "description"
85 election_crypto <- arbitrary
86 election_questions <- resize (fromIntegral maxArbitraryQuestions) $ listOf1 arbitrary
87 election_uuid <- arbitrary
90 { election_hash = hashElection elec
95 [ elec{election_questions}
96 | election_questions <- shrink $ election_questions elec
98 instance Reifies c FFC => Arbitrary (ElectionCrypto c) where
100 let electionCrypto_FFC_params = reflect (Proxy::Proxy c)
101 electionCrypto_FFC_PublicKey <- arbitrary
102 return ElectionCrypto_FFC{..}
104 -- | A type to declare an 'Arbitrary' instance where @b@ depends on @a@.
105 data (:>) a b = a :> b
107 instance Arbitrary (Question :> [Bool]) where
109 quest@Question{..} <- arbitrary
111 let numChoices = List.length question_choices
112 numTrue <- fromIntegral <$> choose (nat question_mini, nat question_maxi)
113 rank <- choose (0, nCk numChoices numTrue - 1)
114 return $ boolsOfCombin numChoices numTrue rank
115 return (quest :> votes)
116 shrink (quest :> votes) =
117 [ q :> shrinkVotes q votes
120 instance Reifies c FFC => Arbitrary (Election c :> [[Bool]]) where
122 elec@Election{..} <- arbitrary
123 votes <- forM election_questions $ \Question{..} -> do
124 let numChoices = List.length question_choices
125 numTrue <- fromIntegral <$> choose (nat question_mini, nat question_maxi)
126 rank <- choose (0, nCk numChoices numTrue - 1)
127 return $ boolsOfCombin numChoices numTrue rank
128 return (elec :> votes)
129 shrink (elec :> votes) =
130 [ e :> List.zipWith shrinkVotes (election_questions e) votes
134 -- | @('boolsOfCombin' nBits nTrue rank)@ returns the 'rank'-th sequence of 'nBits'-bits possible
135 -- with 'nTrue' bits set at 'True' and @(nBits-nTrue)@ set at 'False'.
136 -- @rank@ has to be in @[0 .. 'nCk' nBits nTrue '-' 1]@
137 boolsOfCombin :: Int -> Int -> Int -> [Bool]
138 boolsOfCombin nBits nTrue rank
139 | rank < 0 = undefined
140 | nTrue == 0 = List.replicate nBits False
141 | otherwise = go 0 cs <> List.replicate (nBits-List.last cs) False
143 cs = combinOfRank nBits nTrue rank
146 List.replicate (next-1-curr) False <> [True]
149 -- | @('shrinkVotes' quest votes)@
150 -- returns a reduced version of the given @votes@
151 -- to fit the requirement of the given @quest@.
152 shrinkVotes :: Question -> [Bool] -> [Bool]
153 shrinkVotes Question{..} votes =
154 (\(nTrue, b) -> nTrue <= nat question_maxi && b)
155 <$> List.zip (countTrue votes) votes
157 countTrue :: [Bool] -> [Natural]
158 countTrue = List.foldl' (\ns b -> if b then inc ns else ns) []
161 inc (n:ns) = (n+one):n:ns