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) = groupReify c $
41 testGroup (Text.unpack $ cryptoName (reflect c))
42 [ testProperty "verifyBallot" $ \(seed, (elec::Election crypto v c) :> votes) ->
44 (`evalStateT` mkStdGen seed) $ do
45 -- ballotSecKey :: SecretKey c <- randomSecretKey
46 ballot <- encryptBallot elec Nothing votes
47 unless (verifyBallot elec ballot) $
48 lift $ throwE $ ErrorBallot_Wrong
52 instance Reifies c FFC => Arbitrary (F c) where
53 arbitrary = F <$> choose (zero, fromJust $ fieldCharac @c `minusNaturalMaybe` one)
58 , Multiplicative (G crypto c)
59 , Invertible (G crypto c)
60 ) => Arbitrary (G crypto c) where
67 ) => Arbitrary (E crypto c) where
68 arbitrary = E <$> choose (zero, fromJust $ groupOrder @crypto (Proxy @c) `minusNaturalMaybe` one)
69 instance Arbitrary UUID where
72 (`evalStateT` mkStdGen seed) $
77 , Arbitrary (E crypto c)
78 ) => Arbitrary (Proof crypto v c) where
80 proof_challenge <- arbitrary
81 proof_response <- arbitrary
83 instance Reifies v Version => Arbitrary (Question v) where
85 let question_text = "question"
86 choices :: Natural <- choose (1, maxArbitraryChoices)
87 let question_choices = [Text.pack ("c"<>show c) | c <- [1..choices]]
88 question_mini <- choose (0, choices)
89 question_maxi <- choose (nat question_mini, choices)
92 [ quest{question_choices, question_mini, question_maxi}
93 | question_choices <- shrinkList pure $ question_choices quest
94 , let nChoices = fromIntegral $ List.length question_choices
95 , question_mini <- shrinkIntegral $ min nChoices $ max zero $ question_mini quest
96 , question_maxi <- shrinkIntegral $ min nChoices $ max question_mini $ question_maxi quest
103 , Multiplicative (G crypto c)
104 , Invertible (G crypto c)
106 , JSON.ToJSON (G crypto c)
107 ) => Arbitrary (Election crypto v c) where
109 let election_name = "election"
110 let election_description = "description"
111 let election_crypto = reflect (Proxy @c)
112 election_secret_key <- arbitrary
113 let election_public_key = publicKey election_secret_key
114 election_questions <- resize (fromIntegral maxArbitraryQuestions) $ listOf1 arbitrary
115 election_uuid <- arbitrary
117 { election_hash = hashElection elec
118 , election_version = Just (reflect (Proxy @v))
123 [ elec{election_questions}
124 | election_questions <- shrink $ election_questions elec
127 instance Reifies c FFC => Arbitrary (ElectionCrypto c) where
129 let electionCrypto_FFC_params = reflect (Proxy::Proxy c)
130 electionCrypto_FFC_PublicKey <- arbitrary
131 return ElectionCrypto_FFC{..}
134 -- | A type to declare an 'Arbitrary' instance where @b@ depends on @a@.
135 data (:>) a b = a :> b
137 instance Reifies v Version => Arbitrary (Question v :> [Bool]) where
139 quest@Question{..} <- arbitrary
141 let numChoices = List.length question_choices
142 numTrue <- fromIntegral <$> choose (nat question_mini, nat question_maxi)
143 rank <- choose (0, nCk numChoices numTrue - 1)
144 return $ boolsOfCombin numChoices numTrue rank
145 return (quest :> votes)
146 shrink (quest :> votes) =
147 [ q :> shrinkVotes q votes
156 ) => Arbitrary (Election crypto v c :> [[Bool]]) where
157 arbitrary = groupReify (Proxy @c) $ do
158 elec@Election{..} <- arbitrary
159 votes <- forM election_questions $ \Question{..} -> do
160 let numChoices = List.length question_choices
161 numTrue <- fromIntegral <$> choose (nat question_mini, nat question_maxi)
162 rank <- choose (0, nCk numChoices numTrue - 1)
163 return $ boolsOfCombin numChoices numTrue rank
164 return (elec :> votes)
165 shrink (elec :> votes) = groupReify (Proxy @c) $
166 [ e :> List.zipWith shrinkVotes (election_questions e :: [Question v]) votes
170 -- | @('boolsOfCombin' nBits nTrue rank)@ returns the 'rank'-th sequence of 'nBits'-bits possible
171 -- with 'nTrue' bits set at 'True' and @(nBits-nTrue)@ set at 'False'.
172 -- @rank@ has to be in @[0 .. 'nCk' nBits nTrue '-' 1]@
173 boolsOfCombin :: Int -> Int -> Int -> [Bool]
174 boolsOfCombin nBits nTrue rank
175 | rank < 0 = undefined
176 | nTrue == 0 = List.replicate nBits False
177 | otherwise = go 0 cs <> List.replicate (nBits-List.last cs) False
179 cs = combinOfRank nBits nTrue rank
182 List.replicate (next-1-curr) False <> [True]
185 -- | @('shrinkVotes' quest votes)@
186 -- returns a reduced version of the given @votes@
187 -- to fit the requirement of the given @quest@.
188 shrinkVotes :: Reifies v Version => Question v -> [Bool] -> [Bool]
189 shrinkVotes Question{..} votes =
190 (\(nTrue, b) -> nTrue <= nat question_maxi && b)
191 <$> List.zip (countTrue votes) votes
193 countTrue :: [Bool] -> [Natural]
194 countTrue = List.foldl' (\ns b -> if b then inc ns else ns) []
197 inc (n:ns) = (n+one):n:ns