1 {-# LANGUAGE OverloadedStrings #-}
2 {-# OPTIONS -fno-warn-orphans #-}
3 module QuickCheck.Election where
5 import Test.Tasty.QuickCheck
6 import qualified Data.List as List
7 import qualified Data.Text as Text
8 import Data.Eq (Eq(..))
10 import Data.Ord (Ord(..))
11 import Prelude (undefined)
13 import Protocol.Arithmetic
14 import Protocol.Credential
15 import Protocol.Election
19 -- Hardcoded limits to avoid keep a reasonable testing time.
20 maxArbitraryChoices :: Natural
21 maxArbitraryChoices = 5
22 maxArbitraryQuestions :: Natural
23 maxArbitraryQuestions = 5
25 quickcheck :: TestTree
28 [ testGroup "verifyBallot" $
29 [ testElection @WeakParams
30 , testElection @BeleniosParams
34 testElection :: forall q. Params q => TestTree
36 testGroup (paramsName @q)
37 [ testProperty "Right" $ \(seed, (elec::Election q) :> votes) ->
39 (`evalStateT` mkStdGen seed) $ do
40 -- ballotSecKey :: SecretKey q <- randomSecretKey
41 ballot <- encryptBallot elec Nothing votes
42 unless (verifyBallot elec ballot) $
43 lift $ throwE $ ErrorBallot_WrongNumberOfAnswers 0 0
46 instance PrimeField p => Arbitrary (F p) where
47 arbitrary = choose (zero, F (fieldCharac @p) - one)
48 instance SubGroup q => Arbitrary (G q) where
52 instance SubGroup q => Arbitrary (E q) where
53 arbitrary = E <$> choose (zero, groupOrder @q - one)
54 instance Arbitrary UUID where
57 (`evalStateT` mkStdGen seed) $ do
59 instance SubGroup q => Arbitrary (Proof q) where
61 proof_challenge <- arbitrary
62 proof_response <- arbitrary
64 instance SubGroup q => Arbitrary (Question q) where
66 let question_text = "question"
67 choices :: Natural <- choose (1, maxArbitraryChoices)
68 let question_choices = [Text.pack ("c"<>show c) | c <- [1..choices]]
69 question_mini <- fromNatural <$> choose (0, choices)
70 question_maxi <- fromNatural <$> choose (nat question_mini, choices)
73 [ quest{question_choices, question_mini, question_maxi}
74 | question_choices <- shrinkList pure $ question_choices quest
75 , let nChoices = fromNatural $ fromIntegral $ List.length question_choices
76 , question_mini <- shrink $ min nChoices $ max zero $ question_mini quest
77 , question_maxi <- shrink $ min nChoices $ max question_mini $ question_maxi quest
79 instance SubGroup q => Arbitrary (Election q) where
81 let election_name = "election"
82 let election_description = "description"
83 election_publicKey <- arbitrary
84 election_questions <- resize (fromIntegral maxArbitraryQuestions) $ listOf1 arbitrary
85 election_uuid <- arbitrary
86 let election_hash = Hash ""
89 [ elec{election_questions}
90 | election_questions <- shrink $ election_questions elec
93 -- | A type to declare an 'Arbitrary' instance where @b@ depends on @a@.
94 data (:>) a b = a :> b
96 instance SubGroup q => Arbitrary (Question q :> [Bool]) where
98 quest@Question{..} <- arbitrary
100 let numChoices = List.length question_choices
101 numTrue <- fromIntegral <$> choose (nat question_mini, nat question_maxi)
102 rank <- choose (0, nCk numChoices numTrue - 1)
103 return $ boolsOfCombin numChoices numTrue rank
104 return (quest :> votes)
105 shrink (quest :> votes) =
106 [ q :> shrinkVotes q votes
109 instance SubGroup q => Arbitrary (Election q :> [[Bool]]) where
111 elec@Election{..} <- arbitrary
112 votes <- forM election_questions $ \Question{..} -> do
113 let numChoices = List.length question_choices
114 numTrue <- fromIntegral <$> choose (nat question_mini, nat question_maxi)
115 rank <- choose (0, nCk numChoices numTrue - 1)
116 return $ boolsOfCombin numChoices numTrue rank
117 return (elec :> votes)
118 shrink (elec :> votes) =
119 [ e :> List.zipWith shrinkVotes (election_questions e) votes
123 -- | @('boolsOfCombin' nBits nTrue rank)@ returns the 'rank'-th sequence of 'nBits'-bits possible
124 -- with 'nTrue' bits set at 'True' and @(nBits-nTrue)@ set at 'False'.
125 -- @rank@ has to be in @[0 .. 'nCk' nBits nTrue '-' 1]@
126 boolsOfCombin :: Int -> Int -> Int -> [Bool]
127 boolsOfCombin nBits nTrue rank
128 | rank < 0 = undefined
129 | nTrue == 0 = List.replicate nBits False
130 | otherwise = go 0 cs <> List.replicate (nBits-List.last cs) False
132 cs = combinOfRank nBits nTrue rank
135 List.replicate (next-1-curr) False <> [True]
138 -- | @('shrinkVotes' quest votes)@
139 -- returns a reduced version of the given @votes@
140 -- to fit the requirement of the given @quest@.
141 shrinkVotes :: Question q -> [Bool] -> [Bool]
142 shrinkVotes Question{..} votes =
143 (\(nTrue, b) -> if nTrue <= nat question_maxi then b else False)
144 <$> List.zip (countTrue votes) votes
146 countTrue :: [Bool] -> [Natural]
147 countTrue = List.foldl' (\ns b -> if b then inc ns else ns) []
150 inc (n:ns) = (n+one):n:ns