protocol: polish tally
authorJulien Moutinho <julm+hjugement@autogeree.net>
Tue, 14 May 2019 10:24:29 +0000 (10:24 +0000)
committerJulien Moutinho <julm+hjugement@autogeree.net>
Tue, 14 May 2019 11:10:03 +0000 (11:10 +0000)
hjugement-protocol/hjugement-protocol.cabal
hjugement-protocol/src/Voting/Protocol/Arithmetic.hs
hjugement-protocol/src/Voting/Protocol/Credential.hs
hjugement-protocol/src/Voting/Protocol/Tally.hs
hjugement-protocol/src/Voting/Protocol/Trustee/Indispensable.hs
hjugement-protocol/src/Voting/Protocol/Utils.hs
hjugement-protocol/tests/HUnit/Trustee/Indispensable.hs
hjugement-protocol/tests/QuickCheck/Trustee.hs

index cbf673f659a25b03c616802c16670dd68149d74a..ffd373aff760f6a99829cea62152750d9b6e13c8 100644 (file)
@@ -91,7 +91,6 @@ Library
     TypeApplications
     TypeFamilies
     TypeOperators
-    UndecidableInstances
   ghc-options:
     -Wall
     -Wincomplete-uni-patterns
@@ -150,7 +149,6 @@ Test-Suite hjugement-protocol-test
     TypeApplications
     TypeFamilies
     TypeOperators
-    UndecidableInstances
   ghc-options:
     -Wall
     -Wincomplete-uni-patterns
@@ -200,7 +198,6 @@ Benchmark hjugement-protocol-benchmark
     TypeApplications
     TypeFamilies
     TypeOperators
-    UndecidableInstances
   ghc-options:
     -Wall
     -Wincomplete-uni-patterns
index a8ff6b41fa4be94c624cc9a0bdb8242602dd3fa2..44e730bb4616a3c08187b46a765b4c0ee6c71cbc 100644 (file)
@@ -1,4 +1,5 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
+{-# LANGUAGE UndecidableInstances #-} -- for using 'P' in instance declarations
 module Voting.Protocol.Arithmetic
  ( module Voting.Protocol.Arithmetic
  , Natural
index 39ac87b20c526fc27207c527ae16db9f5dc73ef4..8c2bd6047f604ebedb8c10ee0c21efc4a53eb1bd 100644 (file)
@@ -1,5 +1,6 @@
 {-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
 module Voting.Protocol.Credential where
 
 import Control.DeepSeq (NFData)
@@ -39,7 +40,8 @@ import Voting.Protocol.Arithmetic
 -- The last character is a checksum.
 -- The entropy is: @('tokenLength' * log 'tokenBase' / log 2) '==' 82.01… bits@.
 newtype Credential = Credential Text
- deriving (Eq,Show,Generic,NFData)
+ deriving (Eq,Show,Generic)
+ deriving newtype NFData
 
 credentialAlphabet :: [Char] -- TODO: make this an array
 credentialAlphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
@@ -89,7 +91,8 @@ data CredentialError
 
 -- ** Type 'UUID'
 newtype UUID = UUID Text
- deriving (Eq,Ord,Show,Generic,NFData)
+ deriving (Eq,Ord,Show,Generic)
+ deriving newtype NFData
 
 -- | @'randomUUID'@ generates a random 'UUID'.
 randomUUID ::
index 5c709d2b9fde18555a0a03f90aa8fa89c0371961..f2195b9401cd67a5a02e28f5e0b511b844817a06 100644 (file)
@@ -11,8 +11,7 @@ import Data.Function (($))
 import Data.Functor ((<$>))
 import Data.Maybe (maybe)
 import Data.Semigroup (Semigroup(..))
-import Data.Text (Text)
-import Data.Tuple (fst, uncurry)
+import Data.Tuple (fst)
 import GHC.Generics (Generic)
 import Numeric.Natural (Natural)
 import Prelude (fromIntegral)
@@ -45,7 +44,7 @@ data Tally q = Tally
  } deriving (Eq,Show,Generic,NFData)
 
 -- ** Type 'EncryptedTally'
--- | 'Encryption' by 'Choice' by 'Question'.
+-- | 'Encryption' by choice by 'Question'.
 type EncryptedTally q = [[Encryption q]]
 
 -- | @('encryptedTally' ballots)@
@@ -57,8 +56,7 @@ encryptedTally ballots =
                List.zipWith (\Answer{..} ->
                        List.zipWith (+)
                         (fst <$> answer_opinions))
-                ballot_answers
-        )
+                ballot_answers)
         (List.repeat (List.repeat zero))
         ballots
        , fromIntegral $ List.length ballots
@@ -66,60 +64,50 @@ encryptedTally ballots =
 
 -- ** Type 'DecryptionShareCombinator'
 type DecryptionShareCombinator q =
-       [DecryptionShare q] -> Except ErrorDecryptionShare [[DecryptionFactor q]]
+       EncryptedTally q -> [DecryptionShare q] -> Except ErrorTally [[DecryptionFactor q]]
 
 proveTally ::
  SubGroup q =>
  (EncryptedTally q, Natural) -> [DecryptionShare q] ->
  DecryptionShareCombinator q ->
- Except ErrorDecryptionShare (Tally q)
+ Except ErrorTally (Tally q)
 proveTally
  (tally_encByChoiceByQuest, tally_countMax)
  tally_decShareByTrustee
  decShareCombinator = do
-       decFactorByChoiceByQuest <- decShareCombinator tally_decShareByTrustee
-       dec <- isoZipWithM err
-        (\encByChoice decFactorByChoice ->
-               maybe err return $
-                       isoZipWith (\Encryption{..} decFactor -> encryption_vault / decFactor)
-                        encByChoice
-                        decFactorByChoice)
+       decFactorByChoiceByQuest <- decShareCombinator tally_encByChoiceByQuest tally_decShareByTrustee
+       dec <- isoZipWithM (throwE ErrorTally_NumberOfQuestions)
+        (maybe (throwE ErrorTally_NumberOfChoices) return `o2`
+               isoZipWith (\Encryption{..} decFactor -> encryption_vault / decFactor))
         tally_encByChoiceByQuest
         decFactorByChoiceByQuest
        let logMap = Map.fromList $ List.zip groupGenPowers [0..tally_countMax]
        let log x =
-               maybe (throwE $ ErrorDecryptionShare_InvalidMaxCount) return $
+               maybe (throwE ErrorTally_CannotDecryptCount) return $
                Map.lookup x logMap
        tally_countByChoiceByQuest <- (log `mapM`)`mapM`dec
        return Tally{..}
-       where err = throwE $ ErrorDecryptionShare_Invalid "proveTally"
 
 verifyTally ::
  SubGroup q =>
  Tally q -> DecryptionShareCombinator q ->
- Except ErrorDecryptionShare ()
+ Except ErrorTally ()
 verifyTally Tally{..} decShareCombinator = do
-       decFactorByChoiceByQuest <- decShareCombinator tally_decShareByTrustee
-       isoZipWith3M_ (throwE $ ErrorDecryptionShare_Invalid "verifyTally")
-        (isoZipWith3M_ (throwE $ ErrorDecryptionShare_Invalid "verifyTally")
+       decFactorByChoiceByQuest <- decShareCombinator tally_encByChoiceByQuest tally_decShareByTrustee
+       isoZipWith3M_ (throwE ErrorTally_NumberOfQuestions)
+        (isoZipWith3M_ (throwE ErrorTally_NumberOfChoices)
                 (\Encryption{..} decFactor count -> do
                        let groupGenPowCount = encryption_vault / decFactor
                        unless (groupGenPowCount == groupGen ^ fromNatural count) $
-                               throwE ErrorDecryptionShare_Wrong))
+                               throwE ErrorTally_WrongProof))
         tally_encByChoiceByQuest
         decFactorByChoiceByQuest
         tally_countByChoiceByQuest
 
 -- ** Type 'DecryptionShare'
--- | A decryption share. It is computed by a trustee
--- from its 'SecretKey' share and the 'EncryptedTally',
--- and contains a cryptographic 'Proof' that it hasn't cheated.
-data DecryptionShare q = DecryptionShare
- { decryptionShare_factors :: [[DecryptionFactor q]]
-   -- ^ 'DecryptionFactor' by choice by 'Question'.
- , decryptionShare_proofs  :: [[Proof q]]
-   -- ^ 'Proof's that 'decryptionShare_factors' were correctly computed.
- } deriving (Eq,Show,Generic,NFData)
+-- | A decryption share is a 'DecryptionFactor' and a decryption 'Proof', by choice by 'Question'.
+-- Computed by a trustee in 'proveDecryptionShare'.
+type DecryptionShare q = [[(DecryptionFactor q, Proof q)]]
 
 -- *** Type 'DecryptionFactor'
 -- | @'encryption_nonce' '^'trusteeSecKey@
@@ -129,9 +117,8 @@ type DecryptionFactor = G
 proveDecryptionShare ::
  Monad m => SubGroup q => RandomGen r =>
  EncryptedTally q -> SecretKey q -> S.StateT r m (DecryptionShare q)
-proveDecryptionShare encByChoiceByQuest trusteeSecKey = do
-       res <- (proveDecryptionFactor trusteeSecKey `mapM`) `mapM` encByChoiceByQuest
-       return $ uncurry DecryptionShare $ List.unzip (List.unzip <$> res)
+proveDecryptionShare encByChoiceByQuest trusteeSecKey =
+       (proveDecryptionFactor trusteeSecKey `mapM`) `mapM` encByChoiceByQuest
 
 proveDecryptionFactor ::
  Monad m => SubGroup q => RandomGen r =>
@@ -145,15 +132,20 @@ decryptionShareStatement :: SubGroup q => PublicKey q -> BS.ByteString
 decryptionShareStatement pubKey =
        "decrypt|"<>bytesNat pubKey<>"|"
 
--- *** Type 'ErrorDecryptionShare'
-data ErrorDecryptionShare
- =   ErrorDecryptionShare_Invalid Text
-     -- ^ The number of 'DecryptionFactor's or
-     -- the number of 'Proof's is not the same
-     -- or not the expected number.
- |   ErrorDecryptionShare_Wrong
+-- *** Type 'ErrorTally'
+data ErrorTally
+ =   ErrorTally_NumberOfQuestions
+     -- ^ The number of 'Question's is not the one expected.
+ |   ErrorTally_NumberOfChoices
+     -- ^ The number of choices is not the one expected.
+ |   ErrorTally_NumberOfTrustees
+     -- ^ The number of trustees is not the one expected.
+ |   ErrorTally_WrongProof
      -- ^ The 'Proof' of a 'DecryptionFactor' is wrong.
- |   ErrorDecryptionShare_InvalidMaxCount
+ |   ErrorTally_CannotDecryptCount
+     -- ^ Raised by 'proveTally' when the discrete logarithm of @'groupGen' '^'count@
+     -- cannot be computed, likely because 'tally_countMax' is wrong,
+     -- or because the 'EncryptedTally' or 'DecryptionShare's have not been verified.
  deriving (Eq,Show,Generic,NFData)
 
 -- | @('verifyDecryptionShare' encTally trusteePubKey trusteeDecShare)@
@@ -163,25 +155,22 @@ data ErrorDecryptionShare
 verifyDecryptionShare ::
  Monad m => SubGroup q =>
  EncryptedTally q -> PublicKey q -> DecryptionShare q ->
- ExceptT ErrorDecryptionShare m ()
-verifyDecryptionShare encTally trusteePubKey DecryptionShare{..} =
+ ExceptT ErrorTally m ()
+verifyDecryptionShare encByChoiceByQuest trusteePubKey =
        let zkp = decryptionShareStatement trusteePubKey in
-       isoZipWith3M_ (throwE $ ErrorDecryptionShare_Invalid "verifyDecryptionShare")
-        (isoZipWith3M_ (throwE $ ErrorDecryptionShare_Invalid "verifyDecryptionShare") $
-        \Encryption{..} decFactor proof ->
+       isoZipWithM_ (throwE ErrorTally_NumberOfQuestions)
+        (isoZipWithM_ (throwE ErrorTally_NumberOfChoices) $
+        \Encryption{..} (decFactor, proof) ->
                unless (proof_challenge proof == hash zkp
                 [ commit proof groupGen trusteePubKey
                 , commit proof encryption_nonce decFactor
-                ]) $
-                       throwE ErrorDecryptionShare_Wrong)
-        encTally
-        decryptionShare_factors
-        decryptionShare_proofs
+                ]) $ throwE ErrorTally_WrongProof)
+        encByChoiceByQuest
 
 verifyDecryptionShareByTrustee ::
  Monad m => SubGroup q =>
  EncryptedTally q -> [PublicKey q] -> [DecryptionShare q] ->
- ExceptT ErrorDecryptionShare m ()
+ ExceptT ErrorTally m ()
 verifyDecryptionShareByTrustee encTally =
-       isoZipWithM_ (throwE $ ErrorDecryptionShare_Invalid "verifyDecryptionShare")
+       isoZipWithM_ (throwE ErrorTally_NumberOfTrustees)
         (verifyDecryptionShare encTally)
index bfa475cc8e2a7fb7d545ffe28c6b36888780ceb5..c5491d6f890a2f604c13b4d9782ad82100d1d14a 100644 (file)
@@ -5,8 +5,10 @@ import Control.Monad (Monad(..), foldM, unless)
 import Control.Monad.Trans.Except (ExceptT(..), throwE)
 import Data.Eq (Eq(..))
 import Data.Function (($))
+import Data.Functor ((<$>))
 import Data.Maybe (maybe)
 import Data.Semigroup (Semigroup(..))
+import Data.Tuple (fst)
 import Text.Show (Show(..))
 import qualified Control.Monad.Trans.State.Strict as S
 import qualified Data.ByteString as BS
@@ -39,7 +41,7 @@ data TrusteePublicKey q = TrusteePublicKey
 
 -- ** Type 'ErrorTrusteePublicKey'
 data ErrorTrusteePublicKey
- =   ErrorTrusteePublicKey_Wrong
+ =   ErrorTrusteePublicKey_WrongProof
      -- ^ The 'trustee_SecretKeyProof' is wrong.
  deriving (Eq,Show)
 
@@ -69,7 +71,7 @@ verifyIndispensableTrusteePublicKey TrusteePublicKey{..} =
        hash
         (indispensableTrusteePublicKeyStatement trustee_PublicKey)
         [commit trustee_SecretKeyProof groupGen trustee_PublicKey]) $
-               throwE ErrorTrusteePublicKey_Wrong
+               throwE ErrorTrusteePublicKey_WrongProof
 
 -- ** Hashing
 indispensableTrusteePublicKeyStatement :: PublicKey q -> BS.ByteString
@@ -85,22 +87,20 @@ combineIndispensableTrusteePublicKeys =
 verifyIndispensableDecryptionShareByTrustee ::
  SubGroup q => Monad m =>
  EncryptedTally q -> [PublicKey q] -> [DecryptionShare q] ->
- ExceptT ErrorDecryptionShare m ()
-verifyIndispensableDecryptionShareByTrustee encTally =
-       isoZipWithM_ (throwE $ ErrorDecryptionShare_Invalid "verifyIndispensableDecryptionShareByTrustee")
-        (verifyDecryptionShare encTally)
+ ExceptT ErrorTally m ()
+verifyIndispensableDecryptionShareByTrustee encByChoiceByQuest =
+       isoZipWithM_ (throwE $ ErrorTally_NumberOfTrustees)
+        (verifyDecryptionShare encByChoiceByQuest)
 
 -- | @('combineDecryptionShares' pubKeyByTrustee decShareByTrustee)@
 -- returns the 'DecryptionFactor's by choice by 'Question'
-combineIndispensableDecryptionShares ::
- SubGroup q => [PublicKey q] -> EncryptedTally q -> DecryptionShareCombinator q
-combineIndispensableDecryptionShares pubKeyByTrustee encTally decShareByTrustee = do
-       verifyIndispensableDecryptionShareByTrustee encTally pubKeyByTrustee decShareByTrustee
-       (d0,ds) <- maybe err return $ List.uncons decShareByTrustee
-       foldM
-        (\decFactorByChoiceByQuest DecryptionShare{..} ->
-               isoZipWithM err
-                (\acc df -> maybe err return $ isoZipWith (*) acc df)
-                decFactorByChoiceByQuest decryptionShare_factors)
-        (decryptionShare_factors d0) ds
-       where err = throwE $ ErrorDecryptionShare_Invalid "combineIndispensableDecryptionShares"
+combineIndispensableDecryptionShares :: SubGroup q => [PublicKey q] -> DecryptionShareCombinator q
+combineIndispensableDecryptionShares pubKeyByTrustee encByChoiceByQuest decByChoiceByQuestByTrustee = do
+       verifyIndispensableDecryptionShareByTrustee encByChoiceByQuest pubKeyByTrustee decByChoiceByQuestByTrustee
+       (dec0,decs) <-
+               maybe (throwE ErrorTally_NumberOfTrustees) return $
+               List.uncons decByChoiceByQuestByTrustee
+       foldM (isoZipWithM (throwE ErrorTally_NumberOfQuestions)
+        (maybe (throwE ErrorTally_NumberOfChoices) return `o2`
+               isoZipWith (\a (decFactor, _proof) -> a * decFactor)))
+        ((fst <$>) <$> dec0) decs
index ea1fbb9442b05aca1e91cc9a2ec0619c3112f1a9..1cc30f07bd2462acc446f165f20e15c4f17bd877 100644 (file)
@@ -10,6 +10,12 @@ import Data.Maybe (Maybe(..), maybe)
 import Data.Traversable (Traversable(..))
 import qualified Data.List as List
 
+-- | Like ('.') but with two arguments.
+o2 :: (c -> d) -> (a -> b -> c) -> a -> b -> d
+o2 f g = \x y -> f (g x y)
+infixr 9 `o2`
+{-# INLINE o2 #-}
+
 -- | NOTE: check the lengths before applying @f@.
 isoZipWith :: (a->b->c) -> [a]->[b]->Maybe [c]
 isoZipWith f as bs
@@ -25,38 +31,29 @@ isoZipWith3 f as bs cs
  where al = List.length as
 
 isoZipWithM ::
- Applicative m =>
- m () ->
- (a -> b -> m c) ->
- [a] -> [b] -> m [c]
+ Applicative f =>
+ f () -> (a->b->f c) -> [a]->[b]->f [c]
 isoZipWithM err f as bs =
        maybe ([] <$ err) sequenceA $
                isoZipWith f as bs
 
 isoZipWithM_ ::
- Applicative m =>
- m () ->
- (a -> b -> m c) ->
- [a] -> [b] -> m ()
+ Applicative f =>
+ f () -> (a->b->f c) -> [a]->[b]->f ()
 isoZipWithM_ err f as bs =
        maybe err sequenceA_ $
                isoZipWith f as bs
 
 isoZipWith3M ::
- Applicative m =>
- m () ->
- (a -> b -> c -> m d) ->
- [a] -> [b] -> [c] -> m [d]
+ Applicative f =>
+ f () -> (a->b->c->f d) -> [a]->[b]->[c]->f [d]
 isoZipWith3M err f as bs cs =
        maybe ([] <$ err) sequenceA $
                isoZipWith3 f as bs cs
 
 isoZipWith3M_ ::
- Applicative m =>
- m () ->
- (a -> b -> c -> m d) ->
- [a] -> [b] -> [c] ->
- m ()
+ Applicative f =>
+ f () -> (a->b->c->f d) -> [a]->[b]->[c]->f ()
 isoZipWith3M_ err f as bs cs =
        maybe err sequenceA_ $
                isoZipWith3 f as bs cs
index 3de29c3766a7b4114d233c18669521ed62a61392..e0e0f1759378e2659e3e9307a5bd44fbef8db5c9 100644 (file)
@@ -58,7 +58,7 @@ testVerifyTally ::
  Int -> Natural -> Natural -> Natural -> TestTree
 testVerifyTally seed nTrustees nQuests nChoices =
        let clearTallyResult = dummyTallyResult nQuests nChoices in
-       let decryptedTallyResult :: Either ErrorDecryptionShare [[Natural]] =
+       let decryptedTallyResult :: Either ErrorTally [[Natural]] =
                runExcept $
                (`evalStateT` Random.mkStdGen seed) $ do
                        secKeyByTrustee :: [SecretKey q] <-
@@ -71,9 +71,9 @@ testVerifyTally seed nTrustees nQuests nChoices =
                        lift $ verifyDecryptionShareByTrustee encTally pubKeyByTrustee decShareByTrustee
                        tally@Tally{..} <- lift $
                                proveTally (encTally, countMax) decShareByTrustee $
-                                       combineIndispensableDecryptionShares pubKeyByTrustee encTally
+                                       combineIndispensableDecryptionShares pubKeyByTrustee
                        lift $ verifyTally tally $
-                               combineIndispensableDecryptionShares pubKeyByTrustee encTally
+                               combineIndispensableDecryptionShares pubKeyByTrustee
                        return tally_countByChoiceByQuest
        in
        testCase (Printf.printf "nT=%i,nQ=%i,nC=%i (%i maxCount)"
index 82cfe14213ad129805b01b73756d0d0d0aba7df5..5066e4aa4ecb4589799410c44c137fcb87f1abd9 100644 (file)
@@ -4,7 +4,6 @@ module QuickCheck.Trustee where
 import Test.Tasty.QuickCheck
 
 import Voting.Protocol
-import Voting.Protocol.Trustee.Indispensable
 
 import Utils
 import QuickCheck.Election ()