move to src/ and tests/
authorJulien Moutinho <julm+hjugement@autogeree.net>
Sat, 11 May 2019 23:38:28 +0000 (23:38 +0000)
committerJulien Moutinho <julm+hjugement@autogeree.net>
Sat, 11 May 2019 23:38:28 +0000 (23:38 +0000)
18 files changed:
hjugement-protocol/hjugement-protocol.cabal
hjugement-protocol/src/HLint.hs [moved from hjugement-protocol/Protocol/HLint.hs with 100% similarity]
hjugement-protocol/src/Voting/HLint.hs [new symlink]
hjugement-protocol/src/Voting/Protocol.hs [new file with mode: 0644]
hjugement-protocol/src/Voting/Protocol/Arithmetic.hs [moved from hjugement-protocol/Protocol/Arithmetic.hs with 95% similarity]
hjugement-protocol/src/Voting/Protocol/Credential.hs [moved from hjugement-protocol/Protocol/Credential.hs with 88% similarity]
hjugement-protocol/src/Voting/Protocol/Election.hs [moved from hjugement-protocol/Protocol/Election.hs with 93% similarity]
hjugement-protocol/src/Voting/Protocol/HLint.hs [new symlink]
hjugement-protocol/src/Voting/Protocol/Utils.hs [moved from hjugement-protocol/Protocol/Utils.hs with 72% similarity]
hjugement-protocol/test/HUnit/Utils.hs [deleted file]
hjugement-protocol/tests/HLint.hs [new symlink]
hjugement-protocol/tests/HUnit.hs [moved from hjugement-protocol/test/HUnit.hs with 100% similarity]
hjugement-protocol/tests/HUnit/Arithmetic.hs [moved from hjugement-protocol/test/HUnit/Arithmetic.hs with 96% similarity]
hjugement-protocol/tests/HUnit/Credential.hs [moved from hjugement-protocol/test/HUnit/Credential.hs with 93% similarity]
hjugement-protocol/tests/HUnit/Election.hs [moved from hjugement-protocol/test/HUnit/Election.hs with 73% similarity]
hjugement-protocol/tests/HUnit/HLint.hs [new symlink]
hjugement-protocol/tests/Main.hs [new file with mode: 0644]
hjugement-protocol/tests/Utils.hs [new file with mode: 0644]

index 8b18189872dbbae1baec203fce25abb6ed09a69d..4f3cebed7310a8cd33ba540c61badefcb5f1b806 100644 (file)
@@ -2,7 +2,7 @@ name: hjugement-protocol
 -- PVP:  +-+------- breaking API changes
 --       | | +----- non-breaking API additions
 --       | | | +--- code changes with no API change
-version: 0.0.0.20190501
+version: 0.0.0.20190511
 category: Politic
 synopsis: A cryptographic protocol for the Majority Judgment.
 description:
@@ -40,48 +40,7 @@ description:
     and that the tallying authorities did their job properly (/universal verifiability/).
   .
   * /private/: the identities of the voters who cast a vote are not publicly revealed.
-  .
-  More specifically, in this protocol :
-  .
-  * Ballots are encrypted using public-key cryptography
-    secured by the /Discrete Logarithm problem/:
-    finding @x@ in @g^x `mod` p@, where @p@ is a large prime
-    and @g@ a generator of @Gq@, the multiplicative subgroup of order @q@,
-    in @Fp@ (the finite prime field whose characteristic is @p@).
-    Here, @p@ is 2048-bit and @q@ is 256-bit.
-    The signing (Schnorr-like), the encrypting (ElGamal-like)
-    and the /Decisional Diffe Hellman/ (DDH) assumption,
-    all rely on the hardness of that problem.
-  * Ballots are added without being decrypted
-    because adding (multiplying actually) ciphertexts then decrypting,
-    is like decrypting then adding plaintexts (/additive homomorphism/).
-    Which requires to solve the /Discrete Logarithm Problem/
-    for numbers in the order of the number of voters,
-    which is not hard for small numbers (with a lookup table as here,
-    or with Pollard’s rho algorithm for logarithms).
-  * The /Schnorr protocol/ is used to prove that a voter has knowledge
-    of the secret key used to sign their votes.
-    A voter's credentials is a secret key (the signing key)
-    that has a public part (the verification key).
-    The association between the public part and the corresponding voter’s identity
-    does not need to be known, and actually should not be disclosed to satisfy
-    e.g. the French requirements regarding voting systems.
-    Using credentials prevent the submission of duplicated ballots
-    (because they are added as an additional input to the random oracle
-    in the /non-interactive zero-knowledge/ (NIZK) proofs for ciphertext well-formedness).
-    This allows a testing of duplicates which depends only on the size of the number of voters,
-    and thus enables Helios-C to scale for larger elections while attaining correctness.
-  * The /Chaum-Pedersen protocol/ (proving that equality of discrete logarithms)
-    is used to prove that ciphertexts are well-formed
-    (encrypting a 0 or a 1… or any expected natural) without decrypting them.
-    Which is known as a /Disjunctive Chaum-Pedersen/ proof of partial knowledge.
-  * A /strong Fiat-Shamir transformation/ is used
-    to transform the /interactive zero-knowledge/ (IZK) /Chaum-Pedersen protocol/
-    into a /non-interactive zero-knowledge/ (NIZK) proof, using a SHA256 hash.
-  * (TODO) A Pedersen's /distributed key generation/ (DKG) protocol
-    coupled with ElGamal keys (under the DDH assumption),
-    is used to have a fully distributed semantically secure encryption.
-extra-doc-files: 
+extra-doc-files: README.md
 license: GPL-3
 license-file: COPYING
 stability: experimental
@@ -102,11 +61,13 @@ Source-Repository head
  type:     git
 
 Library
+  hs-source-dirs: src
   exposed-modules:
-    Protocol.Arithmetic
-    Protocol.Credential
-    Protocol.Election
-    Protocol.Utils
+    Voting.Protocol
+    Voting.Protocol.Arithmetic
+    Voting.Protocol.Credential
+    Voting.Protocol.Election
+    Voting.Protocol.Utils
   default-language: Haskell2010
   default-extensions:
     AllowAmbiguousTypes
@@ -144,6 +105,7 @@ Library
     , memory >= 0.14
     -- , mmorph >= 1.1
     -- , monad-classes >= 0.3
+    , deepseq >= 1.4
     , random >= 1.1
     -- , reflection >= 2.1
     , text >= 1.2
@@ -152,7 +114,7 @@ Library
 
 Test-Suite hjugement-protocol-test
   type: exitcode-stdio-1.0
-  hs-source-dirs: test
+  hs-source-dirs: tests
   main-is: Main.hs
   other-modules:
     HUnit
@@ -192,7 +154,7 @@ Test-Suite hjugement-protocol-test
       hjugement-protocol
     , base >= 4.6 && < 5
     , containers >= 0.5
-    , hashable >= 1.2.6
+    -- , hashable >= 1.2.6
     , QuickCheck >= 2.0
     -- , monad-classes >= 0.3
     , random >= 1.1
diff --git a/hjugement-protocol/src/Voting/HLint.hs b/hjugement-protocol/src/Voting/HLint.hs
new file mode 120000 (symlink)
index 0000000..ab18269
--- /dev/null
@@ -0,0 +1 @@
+../HLint.hs
\ No newline at end of file
diff --git a/hjugement-protocol/src/Voting/Protocol.hs b/hjugement-protocol/src/Voting/Protocol.hs
new file mode 100644 (file)
index 0000000..5263493
--- /dev/null
@@ -0,0 +1,11 @@
+module Voting.Protocol
+ ( module Voting.Protocol.Arithmetic
+ , module Voting.Protocol.Credential
+ , module Voting.Protocol.Election
+ , module Voting.Protocol.Trustees
+ ) where
+
+import Voting.Protocol.Arithmetic
+import Voting.Protocol.Credential
+import Voting.Protocol.Election
+import Voting.Protocol.Trustees
similarity index 95%
rename from hjugement-protocol/Protocol/Arithmetic.hs
rename to hjugement-protocol/src/Voting/Protocol/Arithmetic.hs
index ff642e739d4bab81fce5f7c1a7c379746a48a73a..4a6bdeba6fb00517199d392ded86ada8d5967564 100644 (file)
@@ -1,11 +1,12 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
-module Protocol.Arithmetic
- ( module Protocol.Arithmetic
+module Voting.Protocol.Arithmetic
+ ( module Voting.Protocol.Arithmetic
  , Natural
  , Random.RandomGen
  ) where
 
 import Control.Arrow (first)
+import Control.DeepSeq (NFData)
 import Control.Monad (Monad(..))
 import Data.Bits
 import Data.Bool
@@ -17,7 +18,7 @@ import Data.Int (Int)
 import Data.Maybe (Maybe(..))
 import Data.Ord (Ord(..))
 import Data.Semigroup (Semigroup(..))
-import Data.String (IsString(..))
+import Data.String (String, IsString(..))
 import Numeric.Natural (Natural)
 import Prelude (Integer, Integral(..), fromIntegral, Enum(..))
 import Text.Show (Show(..))
@@ -54,7 +55,7 @@ import qualified System.Random as Random
 --
 -- The 'Natural' is always within @[0..'fieldCharac'-1]@.
 newtype F p = F { unF :: Natural }
- deriving (Eq,Ord,Show)
+ deriving (Eq,Ord,Show,NFData)
 
 instance PrimeField p => FromNatural (F p) where
        fromNatural i = F (abs (i `mod` fieldCharac @p))
@@ -140,7 +141,7 @@ class Multiplicative a => Invertible a where
 -- * Type 'G'
 -- | The type of the elements of a 'Multiplicative' 'SubGroup' of a 'PrimeField'.
 newtype G q = G { unG :: F (P q) }
- deriving (Eq,Ord,Show)
+ deriving (Eq,Ord,Show,NFData)
 
 instance PrimeField (P q) => FromNatural (G q) where
        fromNatural = G . fromNatural
@@ -190,6 +191,10 @@ class
                go g = g : go (g * invGen)
                invGen = inv groupGen
 
+groupGenPowers :: SubGroup q => [G q]
+groupGenPowers = go one
+       where go g = g : go (g * groupGen)
+
 -- | @('hash' bs gs)@ returns as a number in 'E'
 -- the SHA256 of the given 'BS.ByteString' 'bs'
 -- prefixing the decimal representation of given 'SubGroup' elements 'gs',
@@ -213,7 +218,7 @@ hash bs gs =
 -- | An exponent of a (necessarily cyclic) 'SubGroup' of a 'PrimeField'.
 -- The value is always in @[0..'groupOrder'-1]@.
 newtype E q = E { unE :: F (P q) }
- deriving (Eq,Ord,Show)
+ deriving (Eq,Ord,Show,NFData)
 
 instance SubGroup q => FromNatural (E q) where
        fromNatural i = E (F (abs (i `mod` unF (groupOrder @q))))
@@ -286,6 +291,14 @@ instance Random.Random Natural where
 
 -- * Groups
 
+-- * Type 'Params'
+class SubGroup q => Params q where
+       paramsName :: String
+instance Params WeakParams where
+       paramsName = "WeakParams"
+instance Params BeleniosParams where
+       paramsName = "BeleniosParams"
+
 -- ** Type 'WeakParams'
 -- | Weak parameters for debugging purposes only.
 data WeakParams
similarity index 88%
rename from hjugement-protocol/Protocol/Credential.hs
rename to hjugement-protocol/src/Voting/Protocol/Credential.hs
index 3b413372d4aa65c743a6a91adef854f1b5ab63c2..9faae845bdcaac86ae1d8e021524d54080f0fc98 100644 (file)
@@ -1,5 +1,8 @@
-module Protocol.Credential where
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+module Voting.Protocol.Credential where
 
+import Control.DeepSeq (NFData)
 import Control.Monad (Monad(..), replicateM)
 import Data.Bits
 import Data.Bool
@@ -12,6 +15,7 @@ import Data.Int (Int)
 import Data.Maybe (maybe)
 import Data.Ord (Ord(..))
 import Data.Text (Text)
+import GHC.Generics (Generic)
 import Numeric.Natural (Natural)
 import Prelude (Integral(..), fromIntegral, div)
 import Text.Show (Show)
@@ -25,7 +29,7 @@ import qualified Data.Text as Text
 import qualified Data.Text.Encoding as Text
 import qualified System.Random as Random
 
-import Protocol.Arithmetic
+import Voting.Protocol.Arithmetic
 
 -- * Type 'Credential'
 -- | A 'Credential' is a word of @('tokenLength'+1 '==' 15)@-characters
@@ -35,7 +39,7 @@ import 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)
+ deriving (Eq,Show,Generic,NFData)
 
 credentialAlphabet :: [Char] -- TODO: make this an array
 credentialAlphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
@@ -84,11 +88,11 @@ data CredentialError
  =   CredentialError_BadChar Char.Char
  |   CredentialError_Checksum
  |   CredentialError_Length
- deriving (Eq, Show)
+ deriving (Eq,Show,Generic,NFData)
 
 -- ** Type 'UUID'
 newtype UUID = UUID Text
- deriving (Eq,Ord,Show)
+ deriving (Eq,Ord,Show,Generic,NFData)
 
 -- | @'randomUUID'@ generates a random 'UUID'.
 randomUUID ::
@@ -104,11 +108,11 @@ randomUUID = do
 -- ** Type 'SecretKey'
 type SecretKey = E
 
--- | @('secretKey' uuid cred)@ returns the 'SecretKey'
+-- | @('credentialSecretKey' uuid cred)@ returns the 'SecretKey'
 -- derived from given 'uuid' and 'cred'
 -- using 'Crypto.fastPBKDF2_SHA256'.
-secretKey :: SubGroup q => UUID -> Credential -> SecretKey q
-secretKey (UUID uuid) (Credential cred) =
+credentialSecretKey :: SubGroup q => UUID -> Credential -> SecretKey q
+credentialSecretKey (UUID uuid) (Credential cred) =
        fromNatural $ BS.foldl'
         (\acc b -> acc`shiftL`3 + fromIntegral b)
         (0::Natural)
similarity index 93%
rename from hjugement-protocol/Protocol/Election.hs
rename to hjugement-protocol/src/Voting/Protocol/Election.hs
index bda06ebfb63a7dcff002fb46c7e81c5f59045631..dff21cd69188db8b43f6a604a543faa9884ce635 100644 (file)
@@ -1,7 +1,10 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE OverloadedStrings #-}
-module Protocol.Election where
+module Voting.Protocol.Election where
 
 import Control.Applicative (Applicative(..))
+import Control.DeepSeq (NFData)
 import Control.Monad (Monad(..), join, mapM, replicateM, unless, zipWithM)
 import Control.Monad.Trans.Class (MonadTrans(..))
 import Control.Monad.Trans.Except (ExceptT, runExcept, throwE, withExceptT)
@@ -12,24 +15,25 @@ import Data.Foldable (Foldable, foldMap, and, sequenceA_)
 import Data.Function (($), (.), id, const)
 import Data.Functor (Functor, (<$>))
 import Data.Functor.Identity (Identity(..))
-import Data.Functor.Compose (Compose(..))
-import Data.Maybe (Maybe(..), fromMaybe)
+import Data.Maybe (Maybe(..), fromMaybe, maybe)
 import Data.Ord (Ord(..))
 import Data.Semigroup (Semigroup(..))
 import Data.Text (Text)
 import Data.Traversable (Traversable(..))
 import Data.Tuple (fst, snd, uncurry)
 import GHC.Natural (minusNaturalMaybe)
+import GHC.Generics (Generic)
 import Numeric.Natural (Natural)
 import Prelude (fromIntegral)
 import Text.Show (Show(..))
 import qualified Control.Monad.Trans.State.Strict as S
 import qualified Data.ByteString as BS
 import qualified Data.List as List
+import qualified Data.Map.Strict as Map
 
-import Protocol.Utils
-import Protocol.Arithmetic
-import Protocol.Credential
+import Voting.Protocol.Utils
+import Voting.Protocol.Arithmetic
+import Voting.Protocol.Credential
 
 -- * Type 'Encryption'
 -- | ElGamal-like encryption.
@@ -50,7 +54,7 @@ data Encryption q = Encryption
  , encryption_vault :: G q
    -- ^ Encrypted 'clear' text,
    -- equal to @('pubKey' '^'encNone '*' 'groupGen' '^'clear)@
- } deriving (Eq,Show)
+ } deriving (Eq,Show,Generic,NFData)
 
 -- | Additive homomorphism.
 -- Using the fact that: @'groupGen' '^'x '*' 'groupGen' '^'y '==' 'groupGen' '^'(x'+'y)@.
@@ -114,9 +118,10 @@ data Proof q = Proof
    -- that is to say: @('proof_response' '==' logBase base 'commitment' '-' sec '*' 'proof_challenge')@,
    -- therefore the prover knows 'sec'.
    --
-   -- The prover choses 'commitment'  to be a random power of @base@,
-   -- to ensure that each 'prove' does not reveal any information about its secret.
- } deriving (Eq,Show)
+   -- The prover choses 'commitment' to be a random power of @base@,
+   -- to ensure that each 'prove' does not reveal any information
+   -- about its secret.
+ } deriving (Eq,Show,Generic,NFData)
 
 -- ** Type 'ZKP'
 -- | Zero-knowledge proof.
@@ -156,10 +161,8 @@ type Oracle list q = list (Commitment q) -> Challenge q
 -- when composing the 'proof_challenge' and 'proof_response' together
 -- (with 'commit').
 --
--- NOTE: @sec@ is @secKey@ in 'signature_proof' or @encNonce@ in 'proveEncryption'.
---
 -- WARNING: for 'prove' to be a so-called /strong Fiat-Shamir transformation/ (not a weak):
--- the statement must be included in the 'hash' (not only the commitments).
+-- the statement must be included in the 'hash' (along with the commitments).
 --
 -- NOTE: a 'random' @nonce@ is used to ensure each 'prove'
 -- does not reveal any information regarding the secret @sec@,
@@ -234,7 +237,7 @@ type Opinion = E
 -- is indexing a 'Disjunction' within a list of them,
 -- without revealing which 'Opinion' it is.
 newtype DisjProof q = DisjProof [Proof q]
- deriving (Eq,Show)
+ deriving (Eq,Show,Generic,NFData)
 
 -- | @('proveEncryption' elecPubKey voterZKP (prevDisjs,nextDisjs) (encNonce,enc))@
 -- returns a 'DisjProof' that 'enc' 'encrypt's
@@ -332,7 +335,7 @@ data Question q = Question
  , question_mini    :: Opinion q
  , question_maxi    :: Opinion q
  -- , question_blank :: Maybe Bool
- } deriving (Eq, Show)
+ } deriving (Eq,Show,Generic,NFData)
 
 -- * Type 'Answer'
 data Answer q = Answer
@@ -343,7 +346,7 @@ data Answer q = Answer
    -- ^ Proofs that the sum of the 'Opinon's encrypted in 'answer_opinions'
    -- is an element of @[mini..maxi]@.
  -- , answer_blankProof ::
- } deriving (Eq,Show)
+ } deriving (Eq,Show,Generic,NFData)
 
 -- | @('encryptAnswer' elecPubKey zkp quest opinions)@
 -- returns an 'Answer' validable by 'verifyAnswer',
@@ -415,7 +418,7 @@ data ErrorAnswer
  |   ErrorAnswer_WrongSumOfOpinions Natural Natural Natural
      -- ^ When the sum of opinions is not within the bounds
      -- of 'question_mini' and 'question_maxi'.
- deriving (Eq,Show)
+ deriving (Eq,Show,Generic,NFData)
 
 -- * Type 'Election'
 data Election q = Election
@@ -425,11 +428,11 @@ data Election q = Election
  , election_questions   :: [Question q]
  , election_uuid        :: UUID
  , election_hash        :: Hash -- TODO: serialize to JSON to calculate this
- } deriving (Eq,Show)
+ } deriving (Eq,Show,Generic,NFData)
 
 -- ** Type 'Hash'
 newtype Hash = Hash Text
- deriving (Eq,Ord,Show)
+ deriving (Eq,Ord,Show,Generic,NFData)
 
 -- * Type 'Ballot'
 data Ballot q = Ballot
@@ -437,9 +440,9 @@ data Ballot q = Ballot
  , ballot_signature     :: Maybe (Signature q)
  , ballot_election_uuid :: UUID
  , ballot_election_hash :: Hash
- }
+ } deriving (Generic,NFData)
 
--- | @('encryptBallot' elec ('Just' secKey) opinionsByQuest)@
+-- | @('encryptBallot' elec ('Just' ballotSecKey) opinionsByQuest)@
 -- returns a 'Ballot' signed by 'secKey' (the voter's secret key)
 -- where 'opinionsByQuest' is a list of 'Opinion's
 -- on each 'question_choices' of each 'election_questions'.
@@ -447,7 +450,7 @@ encryptBallot ::
  Monad m => RandomGen r => SubGroup q =>
  Election q -> Maybe (SecretKey q) -> [[Bool]] ->
  S.StateT r (ExceptT ErrorBallot m) (Ballot q)
-encryptBallot Election{..} secKeyMay opinionsByQuest
+encryptBallot Election{..} ballotSecKeyMay opinionsByQuest
  | List.length election_questions /= List.length opinionsByQuest =
        lift $ throwE $
                ErrorBallot_WrongNumberOfAnswers
@@ -455,21 +458,21 @@ encryptBallot Election{..} secKeyMay opinionsByQuest
                 (fromIntegral $ List.length election_questions)
  | otherwise = do
        let (voterKeys, voterZKP) =
-               case secKeyMay of
+               case ballotSecKeyMay of
                 Nothing -> (Nothing, ZKP "")
-                Just secKey ->
-                       ( Just (secKey, pubKey)
-                       , ZKP (bytesNat pubKey) )
-                       where pubKey = publicKey secKey
+                Just ballotSecKey ->
+                       ( Just (ballotSecKey, ballotPubKey)
+                       , ZKP (bytesNat ballotPubKey) )
+                       where ballotPubKey = publicKey ballotSecKey
        ballot_answers <-
                S.mapStateT (withExceptT ErrorBallot_Answer) $
                        zipWithM (encryptAnswer election_publicKey voterZKP)
                         election_questions opinionsByQuest
        ballot_signature <- case voterKeys of
         Nothing -> return Nothing
-        Just (secKey, signature_publicKey) -> do
+        Just (ballotSecKey, signature_publicKey) -> do
                signature_proof <-
-                       prove secKey (Identity groupGen) $
+                       prove ballotSecKey (Identity groupGen) $
                         \(Identity commitment) ->
                                hash
                                 -- NOTE: the order is unusual, the commitments are first
@@ -514,7 +517,7 @@ data Signature q = Signature
  { signature_publicKey :: PublicKey q
    -- ^ Verification key.
  , signature_proof     :: Proof q
- }
+ } deriving (Generic,NFData)
 
 -- *** Hashing
 
@@ -530,7 +533,8 @@ signatureStatement =
 -- | @('signatureCommitments' voterZKP commitment)@
 signatureCommitments :: SubGroup q => ZKP -> Commitment q -> BS.ByteString
 signatureCommitments (ZKP voterZKP) commitment =
-       "sig|"<>voterZKP<>"|"<>bytesNat commitment<>"|"
+       "sig|"<>voterZKP<>"|" -- NOTE: this is actually part of the statement
+        <> bytesNat commitment<>"|"
 
 -- ** Type 'ErrorBallot'
 -- | Error raised by 'encryptBallot'.
@@ -540,4 +544,4 @@ data ErrorBallot
      -- is different than the number of questions.
  |   ErrorBallot_Answer ErrorAnswer
      -- ^ When 'encryptAnswer' raised an 'ErrorAnswer'.
- deriving (Eq,Show)
+ deriving (Eq,Show,Generic,NFData)
diff --git a/hjugement-protocol/src/Voting/Protocol/HLint.hs b/hjugement-protocol/src/Voting/Protocol/HLint.hs
new file mode 120000 (symlink)
index 0000000..ab18269
--- /dev/null
@@ -0,0 +1 @@
+../HLint.hs
\ No newline at end of file
similarity index 72%
rename from hjugement-protocol/Protocol/Utils.hs
rename to hjugement-protocol/src/Voting/Protocol/Utils.hs
index 6a7fd6f2f077391b773914cf51ba6fd2f59c93c5..fe26d4cec1688e77ea1ef4c1ab8465efda4df03c 100644 (file)
@@ -1,4 +1,5 @@
-module Protocol.Utils where
+module Voting.Protocol.Utils where
+
 import Control.Applicative (Applicative(..), Alternative(..))
 import Control.Monad (Monad(..), join, mapM, unless, zipWithM)
 import Control.Monad.Trans.Class (MonadTrans(..))
@@ -9,21 +10,8 @@ import Data.Foldable (Foldable, foldMap, and, sequenceA_)
 import Data.Function (($), id, const)
 import Data.Functor (Functor, (<$>), (<$))
 import Data.Functor.Identity (Identity(..))
-import Data.Functor.Compose (Compose(..))
-import Data.Maybe (Maybe(..), maybe, fromMaybe)
-import Data.Ord (Ord(..))
-import Data.Semigroup (Semigroup(..))
-import Data.Text (Text)
+import Data.Maybe (Maybe(..), maybe)
 import Data.Traversable (Traversable(..))
-import Data.Tuple (fst, snd, uncurry)
-import GHC.Natural (minusNaturalMaybe)
-import Numeric.Natural (Natural)
-import Prelude (fromIntegral)
-import Text.Show (Show(..))
-import Control.Monad.Trans.Maybe (MaybeT(..))
-import qualified Control.Monad.Trans.Except as Exn
-import qualified Control.Monad.Trans.State.Strict as S
-import qualified Data.ByteString as BS
 import qualified Data.List as List
 
 -- | NOTE: check the lengths before applying @f@.
@@ -49,6 +37,15 @@ isoZipWithM err f as bs =
        maybe ([] <$ err) sequenceA $
                isoZipWith f as bs
 
+isoZipWithM_ ::
+ Applicative m =>
+ m () ->
+ (a -> b -> m c) ->
+ [a] -> [b] -> m ()
+isoZipWithM_ err f as bs =
+       maybe err sequenceA_ $
+               isoZipWith f as bs
+
 isoZipWith3M ::
  Applicative m =>
  m () ->
diff --git a/hjugement-protocol/test/HUnit/Utils.hs b/hjugement-protocol/test/HUnit/Utils.hs
deleted file mode 100644 (file)
index ae9fa89..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-module HUnit.Utils
- ( module Test.Tasty
- , module Test.Tasty.HUnit
- , Monad(..), replicateM, when
- , module Data.Bool
- , Eq(..)
- , Either(..)
- , ($), (.)
- , (<$>)
- , Int
- , Maybe(..)
- , Monoid(..)
- , Ord(..)
- , String
- , Text
- , Word8
- , Num, Fractional(..), Integral(..), Integer, undefined, fromIntegral
- , Show(..)
- ) where
-
-import Control.Monad (Monad(..), replicateM, when)
-import Data.Bool
-import Data.Either (Either(..))
-import Data.Eq (Eq(..))
-import Data.Function (($), (.))
-import Data.Functor ((<$>))
-import Data.Int (Int)
-import Data.Maybe (Maybe(..))
-import Data.Monoid (Monoid(..))
-import Data.Ord (Ord(..))
-import Data.String (String)
-import Data.Text (Text)
-import Data.Word (Word8)
-import Prelude (Num, Fractional(..), Integral(..), Integer, undefined, fromIntegral)
-import Test.Tasty
-import Test.Tasty.HUnit
-import Text.Show (Show(..))
diff --git a/hjugement-protocol/tests/HLint.hs b/hjugement-protocol/tests/HLint.hs
new file mode 120000 (symlink)
index 0000000..ab18269
--- /dev/null
@@ -0,0 +1 @@
+../HLint.hs
\ No newline at end of file
similarity index 96%
rename from hjugement-protocol/test/HUnit/Arithmetic.hs
rename to hjugement-protocol/tests/HUnit/Arithmetic.hs
index cbf279aa04fac052f2f915443eab883e4faa9b27..724e7013776c902a5e75a7363c0e2baac1b26b5e 100644 (file)
@@ -2,8 +2,9 @@
 {-# LANGUAGE OverloadedStrings #-}
 module HUnit.Arithmetic where
 
+import Test.Tasty.HUnit
 import Protocol.Arithmetic
-import HUnit.Utils
+import Utils
 
 hunit :: TestTree
 hunit = testGroup "Arithmetic"
similarity index 93%
rename from hjugement-protocol/test/HUnit/Credential.hs
rename to hjugement-protocol/tests/HUnit/Credential.hs
index 8267b651771b128729ff39b803c2f0e3d3500370..0b63b55416d9d7becb7bcfc863ad2b3cec3e6db9 100644 (file)
@@ -3,12 +3,13 @@
 module HUnit.Credential where
 
 import Control.Applicative (Applicative(..))
+import Test.Tasty.HUnit
 import qualified Control.Monad.Trans.State.Strict as S
 import qualified System.Random as Random
 
 import Protocol.Arithmetic
 import Protocol.Credential
-import HUnit.Utils
+import Utils
 
 hunit :: TestTree
 hunit = testGroup "Credential"
@@ -36,7 +37,7 @@ hunit = testGroup "Credential"
         , "xLcs7ev6Jy6FHHF"  ==> Left CredentialError_Checksum
         , "xLcs7ev6Jy6FHHE"  ==> Right (Credential "xLcs7ev6Jy6FHHE")
         ]
- , testGroup "secretKey" $
+ , testGroup "credentialSecretKey" $
         [ testSecretKey @WeakParams 0 $ E (F 122)
         , testSecretKey @WeakParams 1 $ E (F 35)
         , testSecretKey @BeleniosParams 0 $ E (F 2317630607062989137269685509390)
@@ -50,4 +51,4 @@ testSecretKey seed exp =
                (`S.evalState` Random.mkStdGen seed) $
                        (,) <$> randomUUID <*> randomCredential in
        testCase (show (u,c)) $
-               secretKey @q uuid cred @?= exp
+               credentialSecretKey @q uuid cred @?= exp
similarity index 73%
rename from hjugement-protocol/test/HUnit/Election.hs
rename to hjugement-protocol/tests/HUnit/Election.hs
index c33df3486568f297a98dd0a5c831737d71c7746c..9473d8258597064f7c5e771a9be6022cd438aa86 100644 (file)
@@ -4,24 +4,16 @@
 {-# LANGUAGE PatternSynonyms #-}
 module HUnit.Election where
 
--- import Control.Applicative (Applicative(..))
-import qualified Control.Monad.Trans.Except as Exn
-import qualified Control.Monad.Trans.State.Strict as S
+import Test.Tasty.HUnit
 import qualified Data.List as List
 import qualified System.Random as Random
 
 import Protocol.Arithmetic
 import Protocol.Credential
 import Protocol.Election
-import HUnit.Utils
+import Protocol.Trustees.Simple
 
--- * Type 'Params'
-class SubGroup q => Params q where
-       paramsName :: String
-instance Params WeakParams where
-       paramsName = "WeakParams"
-instance Params BeleniosParams where
-       paramsName = "BeleniosParams"
+import Utils
 
 hunit :: TestTree
 hunit = testGroup "Election"
@@ -37,6 +29,9 @@ hunit = testGroup "Election"
         [ testsEncryptBallot @WeakParams
         , testsEncryptBallot @BeleniosParams
         ]
+ , testGroup "trustee" $
+        [ testsTrustee @WeakParams
+        ]
  ]
 
 testsEncryptBallot :: forall q. Params q => TestTree
@@ -90,22 +85,42 @@ testEncryptBallot ::
  TestTree
 testEncryptBallot seed quests opins exp =
        let verify =
-               Exn.runExcept $
-               (`S.evalStateT` Random.mkStdGen seed) $ do
+               runExcept $
+               (`evalStateT` Random.mkStdGen seed) $ do
                        uuid <- randomUUID
                        cred <- randomCredential
-                       let secKey = secretKey @q uuid cred
-                       let pubKey = publicKey secKey
+                       let ballotSecKey = credentialSecretKey @q uuid cred
+                       let elecPubKey = publicKey ballotSecKey -- FIXME: wrong key
                        let elec = Election
                                 { election_name        = "election"
                                 , election_description = "description"
-                                , election_publicKey   = pubKey
+                                , election_publicKey   = elecPubKey
                                 , election_questions   = quests
                                 , election_uuid        = uuid
-                                , election_hash        = Hash ""
+                                , election_hash        = Hash "" -- FIXME: when implemented
                                 }
                        verifyBallot elec
-                        <$> encryptBallot elec (Just secKey) opins
+                        <$> encryptBallot elec (Just ballotSecKey) opins
        in
        testCase (show opins) $
                verify @?= exp
+
+testsTrustee :: forall q. Params q => TestTree
+testsTrustee =
+       testGroup (paramsName @q)
+        [ testTrustee @q 0 (Right ())
+        ]
+
+testTrustee ::
+ forall q. SubGroup q =>
+ Int -> Either ErrorTrusteePublicKey () -> TestTree
+testTrustee seed exp =
+       let verify =
+               runExcept $
+               (`evalStateT` Random.mkStdGen seed) $ do
+                       trustSecKey <- randomSecretKey @_ @_ @q
+                       trustPubKey <- proveTrusteePublicKey trustSecKey
+                       lift $ verifyTrusteePublicKey trustPubKey
+       in
+       testCase (show seed) $
+               verify @?= exp
diff --git a/hjugement-protocol/tests/HUnit/HLint.hs b/hjugement-protocol/tests/HUnit/HLint.hs
new file mode 120000 (symlink)
index 0000000..ab18269
--- /dev/null
@@ -0,0 +1 @@
+../HLint.hs
\ No newline at end of file
diff --git a/hjugement-protocol/tests/Main.hs b/hjugement-protocol/tests/Main.hs
new file mode 100644 (file)
index 0000000..aa1b21c
--- /dev/null
@@ -0,0 +1,13 @@
+module Main where
+
+import System.IO (IO)
+import Data.Function (($))
+import Test.Tasty
+import HUnit
+
+main :: IO ()
+main =
+       defaultMain $
+       testGroup "Protocol"
+        [ hunits
+        ]
diff --git a/hjugement-protocol/tests/Utils.hs b/hjugement-protocol/tests/Utils.hs
new file mode 100644 (file)
index 0000000..09070e9
--- /dev/null
@@ -0,0 +1,83 @@
+module Utils
+ ( module Test.Tasty
+ , module Data.Bool
+ , Applicative(..)
+ , Monad(..), forM, replicateM, unless, when
+ , Eq(..)
+ , Either(..), either, isLeft, isRight
+ , ($), (.), id, const, flip
+ , (<$>)
+ , Int
+ , Maybe(..)
+ , Monoid(..), Semigroup(..)
+ , Ord(..)
+ , String
+ , Text
+ , Word8
+ , Num, Fractional(..), Integral(..), Integer, fromIntegral
+ , Show(..)
+ , MonadTrans(..)
+ , ExceptT
+ , runExcept
+ , throwE
+ , StateT
+ , evalStateT
+ , mkStdGen
+ , debug
+ , nCk
+ , combinOfRank
+ ) where
+
+import Control.Applicative (Applicative(..))
+import Control.Monad
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.Except
+import Control.Monad.Trans.State.Strict
+import Data.Bool
+import Data.Either (Either(..), either, isLeft, isRight)
+import Data.Eq (Eq(..))
+import Data.Function
+import Data.Functor ((<$>))
+import Data.Int (Int)
+import Data.Maybe (Maybe(..))
+import Data.Monoid (Monoid(..))
+import Data.Ord (Ord(..))
+import Data.Semigroup (Semigroup(..))
+import Data.String (String)
+import Data.Text (Text)
+import Data.Word (Word8)
+import Debug.Trace
+import Prelude (Num(..), Fractional(..), Integral(..), Integer, undefined, fromIntegral)
+import System.Random (mkStdGen)
+import Test.Tasty
+import Text.Show (Show(..))
+
+debug msg x = trace (msg<>": "<>show x) x
+
+-- | @'nCk' n k@ returns the number of combinations
+-- of size 'k' from a set of size 'n'.
+--
+-- Computed using the formula:
+-- @'nCk' n (k+1) == 'nCk' n (k-1) * (n-k+1) / k@
+nCk :: Integral i => i -> i -> i
+n`nCk`k | n<0||k<0||n<k = undefined
+        | otherwise     = go 1 1
+        where
+        go i acc = if k' < i then acc else go (i+1) (acc * (n-i+1) `div` i)
+        -- Use a symmetry to compute over smaller numbers,
+        -- which is more efficient and safer
+        k' = if n`div`2 < k then n-k else k
+
+-- | @'combinOfRank' n k r@ returns the @r@-th combination
+-- of @k@ elements from a set of @n@ elements.
+-- DOC: <http://www.site.uottawa.ca/~lucia/courses/5165-09/GenCombObj.pdf>, p.26
+combinOfRank :: Integral i => i -> i -> i -> [i]
+combinOfRank n k rk | rk<0||n`nCk`k<rk = undefined
+                    | otherwise = for1K 1 1 rk
+       where
+       for1K i j r | i <  k    = uptoRank i j r
+                   | i == k    = [j+r] -- because when i == k, nbCombs is always 1
+                   | otherwise = []
+       uptoRank i j r | nbCombs <- (n-j)`nCk`(k-i)
+                      , nbCombs <= r = uptoRank i (j+1) (r-nbCombs)
+                      | otherwise    = j : for1K (i+1) (j+1) r