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 [ quickcheckElection v weakFFC
32 , quickcheckElection v beleniosFFC
38 Reifies v Version => Proxy v ->
40 quickcheckElection (_v::Proxy v) crypto =
41 reifyCrypto crypto $ \(Proxy::Proxy c) ->
42 testGroup (Text.unpack $ cryptoName crypto)
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
52 instance Reifies c FFC => Arbitrary (F c) where
53 arbitrary = F <$> choose (zero, fromJust $ fieldCharac @c `minusNaturalMaybe` one)
57 , Multiplicative (FieldElement crypto c)
58 ) => Arbitrary (G crypto c) where
65 ) => Arbitrary (E crypto c) where
66 arbitrary = E <$> choose (zero, fromJust $ groupOrder @crypto (Proxy @c) `minusNaturalMaybe` one)
67 instance Arbitrary UUID where
70 (`evalStateT` mkStdGen seed) $
75 , Arbitrary (E crypto c)
76 ) => Arbitrary (Proof crypto v c) where
78 proof_challenge <- arbitrary
79 proof_response <- arbitrary
81 instance Reifies v Version => Arbitrary (Question v) where
83 let question_text = "question"
84 choices :: Natural <- choose (1, maxArbitraryChoices)
85 let question_choices = [Text.pack ("c"<>show c) | c <- [1..choices]]
86 question_mini <- choose (0, choices)
87 question_maxi <- choose (nat question_mini, choices)
90 [ quest{question_choices, question_mini, question_maxi}
91 | question_choices <- shrinkList pure $ question_choices quest
92 , let nChoices = fromIntegral $ List.length question_choices
93 , question_mini <- shrinkIntegral $ min nChoices $ max zero $ question_mini quest
94 , question_maxi <- shrinkIntegral $ min nChoices $ max question_mini $ question_maxi quest
101 , Multiplicative (FieldElement crypto c)
103 , JSON.ToJSON (FieldElement crypto c)
104 ) => Arbitrary (Election crypto v c) where
106 let election_name = "election"
107 let election_description = "description"
108 let election_crypto = reflect (Proxy @c)
109 election_secret_key <- arbitrary
110 let election_public_key = publicKey election_secret_key
111 election_questions <- resize (fromIntegral maxArbitraryQuestions) $ listOf1 arbitrary
112 election_uuid <- arbitrary
114 { election_hash = hashElection elec
115 , election_version = Just (reflect (Proxy @v))
120 [ elec{election_questions}
121 | election_questions <- shrink $ election_questions elec
124 instance Reifies c FFC => Arbitrary (ElectionCrypto c) where
126 let electionCrypto_FFC_params = reflect (Proxy::Proxy c)
127 electionCrypto_FFC_PublicKey <- arbitrary
128 return ElectionCrypto_FFC{..}
131 -- | A type to declare an 'Arbitrary' instance where @b@ depends on @a@.
132 data (:>) a b = a :> b
134 instance Reifies v Version => Arbitrary (Question v :> [Bool]) where
136 quest@Question{..} <- arbitrary
138 let numChoices = List.length question_choices
139 numTrue <- fromIntegral <$> choose (nat question_mini, nat question_maxi)
140 rank <- choose (0, nCk numChoices numTrue - 1)
141 return $ boolsOfCombin numChoices numTrue rank
142 return (quest :> votes)
143 shrink (quest :> votes) =
144 [ q :> shrinkVotes q votes
153 , JSON.ToJSON (FieldElement crypto c)
154 , Multiplicative (FieldElement crypto c)
155 ) => Arbitrary (Election crypto v c :> [[Bool]]) where
157 elec@Election{..} <- arbitrary
158 votes <- forM election_questions $ \Question{..} -> do
159 let numChoices = List.length question_choices
160 numTrue <- fromIntegral <$> choose (nat question_mini, nat question_maxi)
161 rank <- choose (0, nCk numChoices numTrue - 1)
162 return $ boolsOfCombin numChoices numTrue rank
163 return (elec :> votes)
164 shrink (elec :> votes) =
165 [ e :> List.zipWith shrinkVotes (election_questions e :: [Question v]) votes
169 -- | @('boolsOfCombin' nBits nTrue rank)@ returns the 'rank'-th sequence of 'nBits'-bits possible
170 -- with 'nTrue' bits set at 'True' and @(nBits-nTrue)@ set at 'False'.
171 -- @rank@ has to be in @[0 .. 'nCk' nBits nTrue '-' 1]@
172 boolsOfCombin :: Int -> Int -> Int -> [Bool]
173 boolsOfCombin nBits nTrue rank
174 | rank < 0 = undefined
175 | nTrue == 0 = List.replicate nBits False
176 | otherwise = go 0 cs <> List.replicate (nBits-List.last cs) False
178 cs = combinOfRank nBits nTrue rank
181 List.replicate (next-1-curr) False <> [True]
184 -- | @('shrinkVotes' quest votes)@
185 -- returns a reduced version of the given @votes@
186 -- to fit the requirement of the given @quest@.
187 shrinkVotes :: Reifies v Version => Question v -> [Bool] -> [Bool]
188 shrinkVotes Question{..} votes =
189 (\(nTrue, b) -> nTrue <= nat question_maxi && b)
190 <$> List.zip (countTrue votes) votes
192 countTrue :: [Bool] -> [Natural]
193 countTrue = List.foldl' (\ns b -> if b then inc ns else ns) []
196 inc (n:ns) = (n+one):n:ns