hjugement-protocol 0.0.0.20190501 → 0.0.0.20190511
raw patch · 30 files changed
+2139/−1378 lines, 30 filesdep +criteriondep +deepseqdep −hashabledep −mmorphdep ~QuickCheckPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: criterion, deepseq
Dependencies removed: hashable, mmorph
Dependency ranges changed: QuickCheck
API changes (from Hackage documentation)
- Protocol.Arithmetic: (*) :: Multiplicative a => a -> a -> a
- Protocol.Arithmetic: (+) :: Additive a => a -> a -> a
- Protocol.Arithmetic: (-) :: Negable a => a -> a -> a
- Protocol.Arithmetic: (/) :: Invertible a => a -> a -> a
- Protocol.Arithmetic: (^) :: SubGroup q => G q -> E q -> G q
- Protocol.Arithmetic: -- q</tt>.
- Protocol.Arithmetic: -- | Setting <tt>q</tt> determines <tt>p</tt>, equals to <tt><a>P</a>
- Protocol.Arithmetic: E :: F (P q) -> E q
- Protocol.Arithmetic: F :: Natural -> F p
- Protocol.Arithmetic: G :: F (P q) -> G q
- Protocol.Arithmetic: [unE] :: E q -> F (P q)
- Protocol.Arithmetic: [unF] :: F p -> Natural
- Protocol.Arithmetic: [unG] :: G q -> F (P q)
- Protocol.Arithmetic: bytesNat :: ToNatural n => n -> ByteString
- Protocol.Arithmetic: class Additive a
- Protocol.Arithmetic: class FromNatural a
- Protocol.Arithmetic: class Multiplicative a => Invertible a
- Protocol.Arithmetic: class Multiplicative a
- Protocol.Arithmetic: class Additive a => Negable a
- Protocol.Arithmetic: class PrimeField p
- Protocol.Arithmetic: class (PrimeField (P q), Multiplicative (F (P q))) => SubGroup q where {
- Protocol.Arithmetic: class ToNatural a
- Protocol.Arithmetic: data BeleniosParams
- Protocol.Arithmetic: data Natural
- Protocol.Arithmetic: data WeakParams
- Protocol.Arithmetic: fieldCharac :: PrimeField p => Natural
- Protocol.Arithmetic: fromNatural :: FromNatural a => Natural -> a
- Protocol.Arithmetic: groupGen :: SubGroup q => G q
- Protocol.Arithmetic: groupGenInverses :: SubGroup q => [G q]
- Protocol.Arithmetic: groupOrder :: SubGroup q => F (P q)
- Protocol.Arithmetic: hash :: SubGroup q => ByteString -> [G q] -> E q
- Protocol.Arithmetic: infixl 6 +
- Protocol.Arithmetic: infixl 7 *
- Protocol.Arithmetic: infixr 8 ^
- Protocol.Arithmetic: instance (Protocol.Arithmetic.SubGroup q, Protocol.Arithmetic.Additive (Protocol.Arithmetic.F (Protocol.Arithmetic.P q))) => Protocol.Arithmetic.Additive (Protocol.Arithmetic.E q)
- Protocol.Arithmetic: instance (Protocol.Arithmetic.SubGroup q, Protocol.Arithmetic.Multiplicative (Protocol.Arithmetic.F (Protocol.Arithmetic.P q))) => Protocol.Arithmetic.Invertible (Protocol.Arithmetic.G q)
- Protocol.Arithmetic: instance (Protocol.Arithmetic.SubGroup q, Protocol.Arithmetic.Multiplicative (Protocol.Arithmetic.F (Protocol.Arithmetic.P q))) => Protocol.Arithmetic.Multiplicative (Protocol.Arithmetic.E q)
- Protocol.Arithmetic: instance (Protocol.Arithmetic.SubGroup q, Protocol.Arithmetic.Multiplicative (Protocol.Arithmetic.F (Protocol.Arithmetic.P q))) => Protocol.Arithmetic.Multiplicative (Protocol.Arithmetic.G q)
- Protocol.Arithmetic: instance (Protocol.Arithmetic.SubGroup q, Protocol.Arithmetic.Negable (Protocol.Arithmetic.F (Protocol.Arithmetic.P q))) => Protocol.Arithmetic.Negable (Protocol.Arithmetic.E q)
- Protocol.Arithmetic: instance GHC.Classes.Eq (Protocol.Arithmetic.E q)
- Protocol.Arithmetic: instance GHC.Classes.Eq (Protocol.Arithmetic.F p)
- Protocol.Arithmetic: instance GHC.Classes.Eq (Protocol.Arithmetic.G q)
- Protocol.Arithmetic: instance GHC.Classes.Ord (Protocol.Arithmetic.E q)
- Protocol.Arithmetic: instance GHC.Classes.Ord (Protocol.Arithmetic.F p)
- Protocol.Arithmetic: instance GHC.Classes.Ord (Protocol.Arithmetic.G q)
- Protocol.Arithmetic: instance GHC.Show.Show (Protocol.Arithmetic.E q)
- Protocol.Arithmetic: instance GHC.Show.Show (Protocol.Arithmetic.F p)
- Protocol.Arithmetic: instance GHC.Show.Show (Protocol.Arithmetic.G q)
- Protocol.Arithmetic: instance Protocol.Arithmetic.Additive GHC.Integer.Type.Integer
- Protocol.Arithmetic: instance Protocol.Arithmetic.Additive GHC.Natural.Natural
- Protocol.Arithmetic: instance Protocol.Arithmetic.Additive GHC.Types.Int
- Protocol.Arithmetic: instance Protocol.Arithmetic.Multiplicative GHC.Integer.Type.Integer
- Protocol.Arithmetic: instance Protocol.Arithmetic.Multiplicative GHC.Natural.Natural
- Protocol.Arithmetic: instance Protocol.Arithmetic.Multiplicative GHC.Types.Int
- Protocol.Arithmetic: instance Protocol.Arithmetic.Negable GHC.Integer.Type.Integer
- Protocol.Arithmetic: instance Protocol.Arithmetic.Negable GHC.Types.Int
- Protocol.Arithmetic: instance Protocol.Arithmetic.PrimeField (Protocol.Arithmetic.P q) => Protocol.Arithmetic.FromNatural (Protocol.Arithmetic.G q)
- Protocol.Arithmetic: instance Protocol.Arithmetic.PrimeField Protocol.Arithmetic.BeleniosParams
- Protocol.Arithmetic: instance Protocol.Arithmetic.PrimeField Protocol.Arithmetic.WeakParams
- Protocol.Arithmetic: instance Protocol.Arithmetic.PrimeField p => Protocol.Arithmetic.Additive (Protocol.Arithmetic.F p)
- Protocol.Arithmetic: instance Protocol.Arithmetic.PrimeField p => Protocol.Arithmetic.FromNatural (Protocol.Arithmetic.F p)
- Protocol.Arithmetic: instance Protocol.Arithmetic.PrimeField p => Protocol.Arithmetic.Multiplicative (Protocol.Arithmetic.F p)
- Protocol.Arithmetic: instance Protocol.Arithmetic.PrimeField p => Protocol.Arithmetic.Negable (Protocol.Arithmetic.F p)
- Protocol.Arithmetic: instance Protocol.Arithmetic.PrimeField p => System.Random.Random (Protocol.Arithmetic.F p)
- Protocol.Arithmetic: instance Protocol.Arithmetic.SubGroup Protocol.Arithmetic.BeleniosParams
- Protocol.Arithmetic: instance Protocol.Arithmetic.SubGroup Protocol.Arithmetic.WeakParams
- Protocol.Arithmetic: instance Protocol.Arithmetic.SubGroup q => GHC.Enum.Enum (Protocol.Arithmetic.E q)
- Protocol.Arithmetic: instance Protocol.Arithmetic.SubGroup q => Protocol.Arithmetic.FromNatural (Protocol.Arithmetic.E q)
- Protocol.Arithmetic: instance Protocol.Arithmetic.SubGroup q => System.Random.Random (Protocol.Arithmetic.E q)
- Protocol.Arithmetic: instance Protocol.Arithmetic.ToNatural (Protocol.Arithmetic.E q)
- Protocol.Arithmetic: instance Protocol.Arithmetic.ToNatural (Protocol.Arithmetic.F p)
- Protocol.Arithmetic: instance Protocol.Arithmetic.ToNatural (Protocol.Arithmetic.G q)
- Protocol.Arithmetic: instance System.Random.Random GHC.Natural.Natural
- Protocol.Arithmetic: inv :: Invertible a => a -> a
- Protocol.Arithmetic: nat :: ToNatural a => a -> Natural
- Protocol.Arithmetic: neg :: Negable a => a -> a
- Protocol.Arithmetic: newtype E q
- Protocol.Arithmetic: newtype F p
- Protocol.Arithmetic: newtype G q
- Protocol.Arithmetic: one :: Multiplicative a => a
- Protocol.Arithmetic: random :: Monad m => RandomGen r => Random i => Negable i => Multiplicative i => StateT r m i
- Protocol.Arithmetic: randomR :: Monad m => RandomGen r => Random i => Negable i => Multiplicative i => i -> StateT r m i
- Protocol.Arithmetic: sum :: (Additive a, Foldable f) => f a -> a
- Protocol.Arithmetic: type RandomGen = RandomGen
- Protocol.Arithmetic: type family P q :: *;
- Protocol.Arithmetic: zero :: Additive a => a
- Protocol.Arithmetic: }
- Protocol.Credential: Credential :: Text -> Credential
- Protocol.Credential: CredentialError_BadChar :: Char -> CredentialError
- Protocol.Credential: CredentialError_Checksum :: CredentialError
- Protocol.Credential: CredentialError_Length :: CredentialError
- Protocol.Credential: UUID :: Text -> UUID
- Protocol.Credential: credentialAlphabet :: [Char]
- Protocol.Credential: data CredentialError
- Protocol.Credential: instance GHC.Classes.Eq Protocol.Credential.Credential
- Protocol.Credential: instance GHC.Classes.Eq Protocol.Credential.CredentialError
- Protocol.Credential: instance GHC.Classes.Eq Protocol.Credential.UUID
- Protocol.Credential: instance GHC.Classes.Ord Protocol.Credential.UUID
- Protocol.Credential: instance GHC.Show.Show Protocol.Credential.Credential
- Protocol.Credential: instance GHC.Show.Show Protocol.Credential.CredentialError
- Protocol.Credential: instance GHC.Show.Show Protocol.Credential.UUID
- Protocol.Credential: newtype Credential
- Protocol.Credential: newtype UUID
- Protocol.Credential: publicKey :: SubGroup q => SecretKey q -> PublicKey q
- Protocol.Credential: randomCredential :: Monad m => RandomGen r => StateT r m Credential
- Protocol.Credential: randomUUID :: Monad m => RandomGen r => StateT r m UUID
- Protocol.Credential: readCredential :: Text -> Either CredentialError Credential
- Protocol.Credential: secretKey :: SubGroup q => UUID -> Credential -> SecretKey q
- Protocol.Credential: tokenBase :: Int
- Protocol.Credential: tokenLength :: Int
- Protocol.Credential: type PublicKey = G
- Protocol.Credential: type SecretKey = E
- Protocol.Election: Answer :: [(Encryption q, DisjProof q)] -> DisjProof q -> Answer q
- Protocol.Election: Ballot :: [Answer q] -> Maybe (Signature q) -> UUID -> Hash -> Ballot q
- Protocol.Election: DecryptionShare :: [[DecryptionFactor q]] -> [[Proof q]] -> DecryptionShare q
- Protocol.Election: DisjProof :: [Proof q] -> DisjProof q
- Protocol.Election: Election :: Text -> Text -> PublicKey q -> [Question q] -> UUID -> Hash -> Election q
- Protocol.Election: Encryption :: G q -> G q -> Encryption q
- Protocol.Election: ErrorAnswer_WrongNumberOfOpinions :: Natural -> Natural -> ErrorAnswer
- Protocol.Election: ErrorAnswer_WrongSumOfOpinions :: Natural -> Natural -> Natural -> ErrorAnswer
- Protocol.Election: ErrorBallot_Answer :: ErrorAnswer -> ErrorBallot
- Protocol.Election: ErrorBallot_WrongNumberOfAnswers :: Natural -> Natural -> ErrorBallot
- Protocol.Election: ErrorDecryptionShare_Invalid :: ErrorDecryptionShare
- Protocol.Election: ErrorValidateEncryption_InvalidProofLength :: Natural -> Natural -> ErrorValidateEncryption
- Protocol.Election: Hash :: Text -> Hash
- Protocol.Election: Proof :: Challenge q -> E q -> Proof q
- Protocol.Election: Question :: Text -> [Text] -> Opinion q -> Opinion q -> Question q
- Protocol.Election: Signature :: PublicKey q -> Proof q -> Signature q
- Protocol.Election: ZKP :: ByteString -> ZKP
- Protocol.Election: [answer_opinions] :: Answer q -> [(Encryption q, DisjProof q)]
- Protocol.Election: [answer_sumProof] :: Answer q -> DisjProof q
- Protocol.Election: [ballot_answers] :: Ballot q -> [Answer q]
- Protocol.Election: [ballot_election_hash] :: Ballot q -> Hash
- Protocol.Election: [ballot_election_uuid] :: Ballot q -> UUID
- Protocol.Election: [ballot_signature] :: Ballot q -> Maybe (Signature q)
- Protocol.Election: [decryptionShare_factors] :: DecryptionShare q -> [[DecryptionFactor q]]
- Protocol.Election: [decryptionShare_proofs] :: DecryptionShare q -> [[Proof q]]
- Protocol.Election: [election_description] :: Election q -> Text
- Protocol.Election: [election_hash] :: Election q -> Hash
- Protocol.Election: [election_name] :: Election q -> Text
- Protocol.Election: [election_publicKey] :: Election q -> PublicKey q
- Protocol.Election: [election_questions] :: Election q -> [Question q]
- Protocol.Election: [election_uuid] :: Election q -> UUID
- Protocol.Election: [encryption_nonce] :: Encryption q -> G q
- Protocol.Election: [encryption_vault] :: Encryption q -> G q
- Protocol.Election: [proof_challenge] :: Proof q -> Challenge q
- Protocol.Election: [proof_response] :: Proof q -> E q
- Protocol.Election: [question_choices] :: Question q -> [Text]
- Protocol.Election: [question_maxi] :: Question q -> Opinion q
- Protocol.Election: [question_mini] :: Question q -> Opinion q
- Protocol.Election: [question_text] :: Question q -> Text
- Protocol.Election: [signature_proof] :: Signature q -> Proof q
- Protocol.Election: [signature_publicKey] :: Signature q -> PublicKey q
- Protocol.Election: booleanDisjunctions :: SubGroup q => [Disjunction q]
- Protocol.Election: checkDecryptionShare :: Monad m => SubGroup q => RandomGen r => [[Encryption q]] -> PublicKey q -> DecryptionShare q -> ExceptT ErrorDecryptionShare m Bool
- Protocol.Election: commit :: SubGroup q => Proof q -> G q -> G q -> Commitment q
- Protocol.Election: computeDecryptionShare :: Monad m => SubGroup q => RandomGen r => SecretKey q -> [[Encryption q]] -> StateT r m (DecryptionShare q)
- Protocol.Election: data Answer q
- Protocol.Election: data Ballot q
- Protocol.Election: data DecryptionShare q
- Protocol.Election: data Election q
- Protocol.Election: data Encryption q
- Protocol.Election: data ErrorAnswer
- Protocol.Election: data ErrorBallot
- Protocol.Election: data ErrorDecryptionShare
- Protocol.Election: data ErrorValidateEncryption
- Protocol.Election: data Proof q
- Protocol.Election: data Question q
- Protocol.Election: data Signature q
- Protocol.Election: decryptionFactor :: Monad m => SubGroup q => RandomGen r => SecretKey q -> Encryption q -> StateT r m (DecryptionFactor q, Proof q)
- Protocol.Election: decryptionStatement :: SubGroup q => PublicKey q -> ByteString
- Protocol.Election: encrypt :: Monad m => RandomGen r => SubGroup q => PublicKey q -> E q -> StateT r m (EncryptionNonce q, Encryption q)
- Protocol.Election: encryptAnswer :: Monad m => RandomGen r => SubGroup q => PublicKey q -> ZKP -> Question q -> [Bool] -> StateT r (ExceptT ErrorAnswer m) (Answer q)
- Protocol.Election: encryptBallot :: Monad m => RandomGen r => SubGroup q => Election q -> Maybe (SecretKey q) -> [[Bool]] -> StateT r (ExceptT ErrorBallot m) (Ballot q)
- Protocol.Election: encryptionCommitments :: SubGroup q => PublicKey q -> Encryption q -> (Disjunction q, Proof q) -> [G q]
- Protocol.Election: encryptionStatement :: SubGroup q => ZKP -> Encryption q -> ByteString
- Protocol.Election: instance GHC.Classes.Eq (Protocol.Election.Answer q)
- Protocol.Election: instance GHC.Classes.Eq (Protocol.Election.DecryptionShare q)
- Protocol.Election: instance GHC.Classes.Eq (Protocol.Election.DisjProof q)
- Protocol.Election: instance GHC.Classes.Eq (Protocol.Election.Election q)
- Protocol.Election: instance GHC.Classes.Eq (Protocol.Election.Encryption q)
- Protocol.Election: instance GHC.Classes.Eq (Protocol.Election.Proof q)
- Protocol.Election: instance GHC.Classes.Eq (Protocol.Election.Question q)
- Protocol.Election: instance GHC.Classes.Eq Protocol.Election.ErrorAnswer
- Protocol.Election: instance GHC.Classes.Eq Protocol.Election.ErrorBallot
- Protocol.Election: instance GHC.Classes.Eq Protocol.Election.ErrorDecryptionShare
- Protocol.Election: instance GHC.Classes.Eq Protocol.Election.ErrorValidateEncryption
- Protocol.Election: instance GHC.Classes.Eq Protocol.Election.Hash
- Protocol.Election: instance GHC.Classes.Ord Protocol.Election.Hash
- Protocol.Election: instance GHC.Show.Show (Protocol.Election.Answer q)
- Protocol.Election: instance GHC.Show.Show (Protocol.Election.DecryptionShare q)
- Protocol.Election: instance GHC.Show.Show (Protocol.Election.DisjProof q)
- Protocol.Election: instance GHC.Show.Show (Protocol.Election.Election q)
- Protocol.Election: instance GHC.Show.Show (Protocol.Election.Encryption q)
- Protocol.Election: instance GHC.Show.Show (Protocol.Election.Proof q)
- Protocol.Election: instance GHC.Show.Show (Protocol.Election.Question q)
- Protocol.Election: instance GHC.Show.Show Protocol.Election.ErrorAnswer
- Protocol.Election: instance GHC.Show.Show Protocol.Election.ErrorBallot
- Protocol.Election: instance GHC.Show.Show Protocol.Election.ErrorDecryptionShare
- Protocol.Election: instance GHC.Show.Show Protocol.Election.ErrorValidateEncryption
- Protocol.Election: instance GHC.Show.Show Protocol.Election.Hash
- Protocol.Election: instance Protocol.Arithmetic.SubGroup q => Protocol.Arithmetic.Additive (Protocol.Election.Encryption q)
- Protocol.Election: intervalDisjunctions :: SubGroup q => Opinion q -> Opinion q -> [Disjunction q]
- Protocol.Election: newtype DisjProof q
- Protocol.Election: newtype Hash
- Protocol.Election: newtype ZKP
- Protocol.Election: prove :: Monad m => RandomGen r => SubGroup q => Functor list => E q -> list (Commitment q) -> Oracle list q -> StateT r m (Proof q)
- Protocol.Election: proveEncryption :: forall m r q. Monad m => RandomGen r => SubGroup q => PublicKey q -> ZKP -> ([Disjunction q], [Disjunction q]) -> (EncryptionNonce q, Encryption q) -> StateT r m (DisjProof q)
- Protocol.Election: signatureCommitments :: SubGroup q => ZKP -> Commitment q -> ByteString
- Protocol.Election: signatureStatement :: Foldable f => SubGroup q => f (Answer q) -> [G q]
- Protocol.Election: type Challenge = E
- Protocol.Election: type Commitment = G
- Protocol.Election: type DecryptionFactor = G
- Protocol.Election: type Disjunction = G
- Protocol.Election: type EncryptionNonce = E
- Protocol.Election: type Opinion = E
- Protocol.Election: type Oracle list q = list (Commitment q) -> Challenge q
- Protocol.Election: verifyAnswer :: SubGroup q => PublicKey q -> ZKP -> Question q -> Answer q -> Bool
- Protocol.Election: verifyBallot :: SubGroup q => Election q -> Ballot q -> Bool
- Protocol.Election: verifyEncryption :: Monad m => SubGroup q => PublicKey q -> ZKP -> [Disjunction q] -> (Encryption q, DisjProof q) -> ExceptT ErrorValidateEncryption m Bool
+ Voting.Protocol.Arithmetic: (*) :: Multiplicative a => a -> a -> a
+ Voting.Protocol.Arithmetic: (+) :: Additive a => a -> a -> a
+ Voting.Protocol.Arithmetic: (-) :: Negable a => a -> a -> a
+ Voting.Protocol.Arithmetic: (/) :: Invertible a => a -> a -> a
+ Voting.Protocol.Arithmetic: (^) :: SubGroup q => G q -> E q -> G q
+ Voting.Protocol.Arithmetic: -- q</tt>.
+ Voting.Protocol.Arithmetic: -- | Setting <tt>q</tt> determines <tt>p</tt>, equals to <tt><a>P</a>
+ Voting.Protocol.Arithmetic: E :: F (P q) -> E q
+ Voting.Protocol.Arithmetic: F :: Natural -> F p
+ Voting.Protocol.Arithmetic: G :: F (P q) -> G q
+ Voting.Protocol.Arithmetic: [unE] :: E q -> F (P q)
+ Voting.Protocol.Arithmetic: [unF] :: F p -> Natural
+ Voting.Protocol.Arithmetic: [unG] :: G q -> F (P q)
+ Voting.Protocol.Arithmetic: bytesNat :: ToNatural n => n -> ByteString
+ Voting.Protocol.Arithmetic: class Additive a
+ Voting.Protocol.Arithmetic: class FromNatural a
+ Voting.Protocol.Arithmetic: class Multiplicative a => Invertible a
+ Voting.Protocol.Arithmetic: class Multiplicative a
+ Voting.Protocol.Arithmetic: class Additive a => Negable a
+ Voting.Protocol.Arithmetic: class SubGroup q => Params q
+ Voting.Protocol.Arithmetic: class PrimeField p
+ Voting.Protocol.Arithmetic: class RandomGen g
+ Voting.Protocol.Arithmetic: class (PrimeField (P q), Multiplicative (F (P q))) => SubGroup q where {
+ Voting.Protocol.Arithmetic: class ToNatural a
+ Voting.Protocol.Arithmetic: data BeleniosParams
+ Voting.Protocol.Arithmetic: data Natural
+ Voting.Protocol.Arithmetic: data WeakParams
+ Voting.Protocol.Arithmetic: fieldCharac :: PrimeField p => Natural
+ Voting.Protocol.Arithmetic: fromNatural :: FromNatural a => Natural -> a
+ Voting.Protocol.Arithmetic: groupGen :: SubGroup q => G q
+ Voting.Protocol.Arithmetic: groupGenInverses :: SubGroup q => [G q]
+ Voting.Protocol.Arithmetic: groupGenPowers :: SubGroup q => [G q]
+ Voting.Protocol.Arithmetic: groupOrder :: SubGroup q => F (P q)
+ Voting.Protocol.Arithmetic: hash :: SubGroup q => ByteString -> [G q] -> E q
+ Voting.Protocol.Arithmetic: infixl 6 +
+ Voting.Protocol.Arithmetic: infixl 7 *
+ Voting.Protocol.Arithmetic: infixr 8 ^
+ Voting.Protocol.Arithmetic: instance (Voting.Protocol.Arithmetic.SubGroup q, Voting.Protocol.Arithmetic.Additive (Voting.Protocol.Arithmetic.F (Voting.Protocol.Arithmetic.P q))) => Voting.Protocol.Arithmetic.Additive (Voting.Protocol.Arithmetic.E q)
+ Voting.Protocol.Arithmetic: instance (Voting.Protocol.Arithmetic.SubGroup q, Voting.Protocol.Arithmetic.Multiplicative (Voting.Protocol.Arithmetic.F (Voting.Protocol.Arithmetic.P q))) => Voting.Protocol.Arithmetic.Invertible (Voting.Protocol.Arithmetic.G q)
+ Voting.Protocol.Arithmetic: instance (Voting.Protocol.Arithmetic.SubGroup q, Voting.Protocol.Arithmetic.Multiplicative (Voting.Protocol.Arithmetic.F (Voting.Protocol.Arithmetic.P q))) => Voting.Protocol.Arithmetic.Multiplicative (Voting.Protocol.Arithmetic.E q)
+ Voting.Protocol.Arithmetic: instance (Voting.Protocol.Arithmetic.SubGroup q, Voting.Protocol.Arithmetic.Multiplicative (Voting.Protocol.Arithmetic.F (Voting.Protocol.Arithmetic.P q))) => Voting.Protocol.Arithmetic.Multiplicative (Voting.Protocol.Arithmetic.G q)
+ Voting.Protocol.Arithmetic: instance (Voting.Protocol.Arithmetic.SubGroup q, Voting.Protocol.Arithmetic.Negable (Voting.Protocol.Arithmetic.F (Voting.Protocol.Arithmetic.P q))) => Voting.Protocol.Arithmetic.Negable (Voting.Protocol.Arithmetic.E q)
+ Voting.Protocol.Arithmetic: instance Control.DeepSeq.NFData (Voting.Protocol.Arithmetic.E q)
+ Voting.Protocol.Arithmetic: instance Control.DeepSeq.NFData (Voting.Protocol.Arithmetic.F p)
+ Voting.Protocol.Arithmetic: instance Control.DeepSeq.NFData (Voting.Protocol.Arithmetic.G q)
+ Voting.Protocol.Arithmetic: instance GHC.Classes.Eq (Voting.Protocol.Arithmetic.E q)
+ Voting.Protocol.Arithmetic: instance GHC.Classes.Eq (Voting.Protocol.Arithmetic.F p)
+ Voting.Protocol.Arithmetic: instance GHC.Classes.Eq (Voting.Protocol.Arithmetic.G q)
+ Voting.Protocol.Arithmetic: instance GHC.Classes.Ord (Voting.Protocol.Arithmetic.E q)
+ Voting.Protocol.Arithmetic: instance GHC.Classes.Ord (Voting.Protocol.Arithmetic.F p)
+ Voting.Protocol.Arithmetic: instance GHC.Classes.Ord (Voting.Protocol.Arithmetic.G q)
+ Voting.Protocol.Arithmetic: instance GHC.Show.Show (Voting.Protocol.Arithmetic.E q)
+ Voting.Protocol.Arithmetic: instance GHC.Show.Show (Voting.Protocol.Arithmetic.F p)
+ Voting.Protocol.Arithmetic: instance GHC.Show.Show (Voting.Protocol.Arithmetic.G q)
+ Voting.Protocol.Arithmetic: instance System.Random.Random GHC.Natural.Natural
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.Additive GHC.Integer.Type.Integer
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.Additive GHC.Natural.Natural
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.Additive GHC.Types.Int
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.Multiplicative GHC.Integer.Type.Integer
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.Multiplicative GHC.Natural.Natural
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.Multiplicative GHC.Types.Int
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.Negable GHC.Integer.Type.Integer
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.Negable GHC.Types.Int
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.Params Voting.Protocol.Arithmetic.BeleniosParams
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.Params Voting.Protocol.Arithmetic.WeakParams
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.PrimeField (Voting.Protocol.Arithmetic.P q) => Voting.Protocol.Arithmetic.FromNatural (Voting.Protocol.Arithmetic.G q)
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.PrimeField Voting.Protocol.Arithmetic.BeleniosParams
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.PrimeField Voting.Protocol.Arithmetic.WeakParams
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.PrimeField p => System.Random.Random (Voting.Protocol.Arithmetic.F p)
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.PrimeField p => Voting.Protocol.Arithmetic.Additive (Voting.Protocol.Arithmetic.F p)
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.PrimeField p => Voting.Protocol.Arithmetic.FromNatural (Voting.Protocol.Arithmetic.F p)
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.PrimeField p => Voting.Protocol.Arithmetic.Multiplicative (Voting.Protocol.Arithmetic.F p)
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.PrimeField p => Voting.Protocol.Arithmetic.Negable (Voting.Protocol.Arithmetic.F p)
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.SubGroup Voting.Protocol.Arithmetic.BeleniosParams
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.SubGroup Voting.Protocol.Arithmetic.WeakParams
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.SubGroup q => GHC.Enum.Enum (Voting.Protocol.Arithmetic.E q)
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.SubGroup q => System.Random.Random (Voting.Protocol.Arithmetic.E q)
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.SubGroup q => Voting.Protocol.Arithmetic.FromNatural (Voting.Protocol.Arithmetic.E q)
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.ToNatural (Voting.Protocol.Arithmetic.E q)
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.ToNatural (Voting.Protocol.Arithmetic.F p)
+ Voting.Protocol.Arithmetic: instance Voting.Protocol.Arithmetic.ToNatural (Voting.Protocol.Arithmetic.G q)
+ Voting.Protocol.Arithmetic: inv :: Invertible a => a -> a
+ Voting.Protocol.Arithmetic: nat :: ToNatural a => a -> Natural
+ Voting.Protocol.Arithmetic: neg :: Negable a => a -> a
+ Voting.Protocol.Arithmetic: newtype E q
+ Voting.Protocol.Arithmetic: newtype F p
+ Voting.Protocol.Arithmetic: newtype G q
+ Voting.Protocol.Arithmetic: one :: Multiplicative a => a
+ Voting.Protocol.Arithmetic: paramsName :: Params q => String
+ Voting.Protocol.Arithmetic: random :: Monad m => RandomGen r => Random i => Negable i => Multiplicative i => StateT r m i
+ Voting.Protocol.Arithmetic: randomR :: Monad m => RandomGen r => Random i => Negable i => Multiplicative i => i -> StateT r m i
+ Voting.Protocol.Arithmetic: sum :: (Additive a, Foldable f) => f a -> a
+ Voting.Protocol.Arithmetic: type family P q :: *;
+ Voting.Protocol.Arithmetic: zero :: Additive a => a
+ Voting.Protocol.Arithmetic: }
+ Voting.Protocol.Credential: Credential :: Text -> Credential
+ Voting.Protocol.Credential: CredentialError_BadChar :: Char -> CredentialError
+ Voting.Protocol.Credential: CredentialError_Checksum :: CredentialError
+ Voting.Protocol.Credential: CredentialError_Length :: CredentialError
+ Voting.Protocol.Credential: UUID :: Text -> UUID
+ Voting.Protocol.Credential: credentialAlphabet :: [Char]
+ Voting.Protocol.Credential: credentialSecretKey :: SubGroup q => UUID -> Credential -> SecretKey q
+ Voting.Protocol.Credential: data CredentialError
+ Voting.Protocol.Credential: instance Control.DeepSeq.NFData Voting.Protocol.Credential.Credential
+ Voting.Protocol.Credential: instance Control.DeepSeq.NFData Voting.Protocol.Credential.CredentialError
+ Voting.Protocol.Credential: instance Control.DeepSeq.NFData Voting.Protocol.Credential.UUID
+ Voting.Protocol.Credential: instance GHC.Classes.Eq Voting.Protocol.Credential.Credential
+ Voting.Protocol.Credential: instance GHC.Classes.Eq Voting.Protocol.Credential.CredentialError
+ Voting.Protocol.Credential: instance GHC.Classes.Eq Voting.Protocol.Credential.UUID
+ Voting.Protocol.Credential: instance GHC.Classes.Ord Voting.Protocol.Credential.UUID
+ Voting.Protocol.Credential: instance GHC.Generics.Generic Voting.Protocol.Credential.Credential
+ Voting.Protocol.Credential: instance GHC.Generics.Generic Voting.Protocol.Credential.CredentialError
+ Voting.Protocol.Credential: instance GHC.Generics.Generic Voting.Protocol.Credential.UUID
+ Voting.Protocol.Credential: instance GHC.Show.Show Voting.Protocol.Credential.Credential
+ Voting.Protocol.Credential: instance GHC.Show.Show Voting.Protocol.Credential.CredentialError
+ Voting.Protocol.Credential: instance GHC.Show.Show Voting.Protocol.Credential.UUID
+ Voting.Protocol.Credential: newtype Credential
+ Voting.Protocol.Credential: newtype UUID
+ Voting.Protocol.Credential: publicKey :: SubGroup q => SecretKey q -> PublicKey q
+ Voting.Protocol.Credential: randomCredential :: Monad m => RandomGen r => StateT r m Credential
+ Voting.Protocol.Credential: randomUUID :: Monad m => RandomGen r => StateT r m UUID
+ Voting.Protocol.Credential: readCredential :: Text -> Either CredentialError Credential
+ Voting.Protocol.Credential: tokenBase :: Int
+ Voting.Protocol.Credential: tokenLength :: Int
+ Voting.Protocol.Credential: type PublicKey = G
+ Voting.Protocol.Credential: type SecretKey = E
+ Voting.Protocol.Election: Answer :: [(Encryption q, DisjProof q)] -> DisjProof q -> Answer q
+ Voting.Protocol.Election: Ballot :: [Answer q] -> Maybe (Signature q) -> UUID -> Hash -> Ballot q
+ Voting.Protocol.Election: DecryptionShare :: [[DecryptionFactor q]] -> [[Proof q]] -> DecryptionShare q
+ Voting.Protocol.Election: DisjProof :: [Proof q] -> DisjProof q
+ Voting.Protocol.Election: Election :: Text -> Text -> PublicKey q -> [Question q] -> UUID -> Hash -> Election q
+ Voting.Protocol.Election: Encryption :: G q -> G q -> Encryption q
+ Voting.Protocol.Election: ErrorAnswer_WrongNumberOfOpinions :: Natural -> Natural -> ErrorAnswer
+ Voting.Protocol.Election: ErrorAnswer_WrongSumOfOpinions :: Natural -> Natural -> Natural -> ErrorAnswer
+ Voting.Protocol.Election: ErrorBallot_Answer :: ErrorAnswer -> ErrorBallot
+ Voting.Protocol.Election: ErrorBallot_WrongNumberOfAnswers :: Natural -> Natural -> ErrorBallot
+ Voting.Protocol.Election: ErrorDecryptionShare_Invalid :: ErrorDecryptionShare
+ Voting.Protocol.Election: ErrorDecryptionShare_Wrong :: ErrorDecryptionShare
+ Voting.Protocol.Election: ErrorVerifyEncryption_InvalidProofLength :: Natural -> Natural -> ErrorVerifyEncryption
+ Voting.Protocol.Election: Hash :: Text -> Hash
+ Voting.Protocol.Election: Proof :: Challenge q -> E q -> Proof q
+ Voting.Protocol.Election: Question :: Text -> [Text] -> Opinion q -> Opinion q -> Question q
+ Voting.Protocol.Election: Signature :: PublicKey q -> Proof q -> Signature q
+ Voting.Protocol.Election: Tally :: Natural -> [[Encryption q]] -> [DecryptionShare q] -> [[Natural]] -> Tally q
+ Voting.Protocol.Election: ZKP :: ByteString -> ZKP
+ Voting.Protocol.Election: [answer_opinions] :: Answer q -> [(Encryption q, DisjProof q)]
+ Voting.Protocol.Election: [answer_sumProof] :: Answer q -> DisjProof q
+ Voting.Protocol.Election: [ballot_answers] :: Ballot q -> [Answer q]
+ Voting.Protocol.Election: [ballot_election_hash] :: Ballot q -> Hash
+ Voting.Protocol.Election: [ballot_election_uuid] :: Ballot q -> UUID
+ Voting.Protocol.Election: [ballot_signature] :: Ballot q -> Maybe (Signature q)
+ Voting.Protocol.Election: [decryptionShare_factors] :: DecryptionShare q -> [[DecryptionFactor q]]
+ Voting.Protocol.Election: [decryptionShare_proofs] :: DecryptionShare q -> [[Proof q]]
+ Voting.Protocol.Election: [election_description] :: Election q -> Text
+ Voting.Protocol.Election: [election_hash] :: Election q -> Hash
+ Voting.Protocol.Election: [election_name] :: Election q -> Text
+ Voting.Protocol.Election: [election_publicKey] :: Election q -> PublicKey q
+ Voting.Protocol.Election: [election_questions] :: Election q -> [Question q]
+ Voting.Protocol.Election: [election_uuid] :: Election q -> UUID
+ Voting.Protocol.Election: [encryption_nonce] :: Encryption q -> G q
+ Voting.Protocol.Election: [encryption_vault] :: Encryption q -> G q
+ Voting.Protocol.Election: [proof_challenge] :: Proof q -> Challenge q
+ Voting.Protocol.Election: [proof_response] :: Proof q -> E q
+ Voting.Protocol.Election: [question_choices] :: Question q -> [Text]
+ Voting.Protocol.Election: [question_maxi] :: Question q -> Opinion q
+ Voting.Protocol.Election: [question_mini] :: Question q -> Opinion q
+ Voting.Protocol.Election: [question_text] :: Question q -> Text
+ Voting.Protocol.Election: [signature_proof] :: Signature q -> Proof q
+ Voting.Protocol.Election: [signature_publicKey] :: Signature q -> PublicKey q
+ Voting.Protocol.Election: [tally_countByQuestByBallot] :: Tally q -> [[Natural]]
+ Voting.Protocol.Election: [tally_decShareByTrustee] :: Tally q -> [DecryptionShare q]
+ Voting.Protocol.Election: [tally_encByQuestByBallot] :: Tally q -> [[Encryption q]]
+ Voting.Protocol.Election: [tally_numBallots] :: Tally q -> Natural
+ Voting.Protocol.Election: booleanDisjunctions :: SubGroup q => [Disjunction q]
+ Voting.Protocol.Election: commit :: SubGroup q => Proof q -> G q -> G q -> Commitment q
+ Voting.Protocol.Election: data Answer q
+ Voting.Protocol.Election: data Ballot q
+ Voting.Protocol.Election: data DecryptionShare q
+ Voting.Protocol.Election: data Election q
+ Voting.Protocol.Election: data Encryption q
+ Voting.Protocol.Election: data ErrorAnswer
+ Voting.Protocol.Election: data ErrorBallot
+ Voting.Protocol.Election: data ErrorDecryptionShare
+ Voting.Protocol.Election: data ErrorVerifyEncryption
+ Voting.Protocol.Election: data Proof q
+ Voting.Protocol.Election: data Question q
+ Voting.Protocol.Election: data Signature q
+ Voting.Protocol.Election: data Tally q
+ Voting.Protocol.Election: decryptionShareStatement :: SubGroup q => PublicKey q -> ByteString
+ Voting.Protocol.Election: encrypt :: Monad m => RandomGen r => SubGroup q => PublicKey q -> E q -> StateT r m (EncryptionNonce q, Encryption q)
+ Voting.Protocol.Election: encryptAnswer :: Monad m => RandomGen r => SubGroup q => PublicKey q -> ZKP -> Question q -> [Bool] -> StateT r (ExceptT ErrorAnswer m) (Answer q)
+ Voting.Protocol.Election: encryptBallot :: Monad m => RandomGen r => SubGroup q => Election q -> Maybe (SecretKey q) -> [[Bool]] -> StateT r (ExceptT ErrorBallot m) (Ballot q)
+ Voting.Protocol.Election: encryptionCommitments :: SubGroup q => PublicKey q -> Encryption q -> Disjunction q -> Proof q -> [G q]
+ Voting.Protocol.Election: encryptionStatement :: SubGroup q => ZKP -> Encryption q -> ByteString
+ Voting.Protocol.Election: fakeProof :: Monad m => RandomGen r => SubGroup q => StateT r m (Proof q)
+ Voting.Protocol.Election: instance Control.DeepSeq.NFData (Voting.Protocol.Election.Answer q)
+ Voting.Protocol.Election: instance Control.DeepSeq.NFData (Voting.Protocol.Election.Ballot q)
+ Voting.Protocol.Election: instance Control.DeepSeq.NFData (Voting.Protocol.Election.DecryptionShare q)
+ Voting.Protocol.Election: instance Control.DeepSeq.NFData (Voting.Protocol.Election.DisjProof q)
+ Voting.Protocol.Election: instance Control.DeepSeq.NFData (Voting.Protocol.Election.Election q)
+ Voting.Protocol.Election: instance Control.DeepSeq.NFData (Voting.Protocol.Election.Encryption q)
+ Voting.Protocol.Election: instance Control.DeepSeq.NFData (Voting.Protocol.Election.Proof q)
+ Voting.Protocol.Election: instance Control.DeepSeq.NFData (Voting.Protocol.Election.Question q)
+ Voting.Protocol.Election: instance Control.DeepSeq.NFData (Voting.Protocol.Election.Signature q)
+ Voting.Protocol.Election: instance Control.DeepSeq.NFData (Voting.Protocol.Election.Tally q)
+ Voting.Protocol.Election: instance Control.DeepSeq.NFData Voting.Protocol.Election.ErrorAnswer
+ Voting.Protocol.Election: instance Control.DeepSeq.NFData Voting.Protocol.Election.ErrorBallot
+ Voting.Protocol.Election: instance Control.DeepSeq.NFData Voting.Protocol.Election.ErrorDecryptionShare
+ Voting.Protocol.Election: instance Control.DeepSeq.NFData Voting.Protocol.Election.Hash
+ Voting.Protocol.Election: instance GHC.Classes.Eq (Voting.Protocol.Election.Answer q)
+ Voting.Protocol.Election: instance GHC.Classes.Eq (Voting.Protocol.Election.DecryptionShare q)
+ Voting.Protocol.Election: instance GHC.Classes.Eq (Voting.Protocol.Election.DisjProof q)
+ Voting.Protocol.Election: instance GHC.Classes.Eq (Voting.Protocol.Election.Election q)
+ Voting.Protocol.Election: instance GHC.Classes.Eq (Voting.Protocol.Election.Encryption q)
+ Voting.Protocol.Election: instance GHC.Classes.Eq (Voting.Protocol.Election.Proof q)
+ Voting.Protocol.Election: instance GHC.Classes.Eq (Voting.Protocol.Election.Question q)
+ Voting.Protocol.Election: instance GHC.Classes.Eq (Voting.Protocol.Election.Tally q)
+ Voting.Protocol.Election: instance GHC.Classes.Eq Voting.Protocol.Election.ErrorAnswer
+ Voting.Protocol.Election: instance GHC.Classes.Eq Voting.Protocol.Election.ErrorBallot
+ Voting.Protocol.Election: instance GHC.Classes.Eq Voting.Protocol.Election.ErrorDecryptionShare
+ Voting.Protocol.Election: instance GHC.Classes.Eq Voting.Protocol.Election.ErrorVerifyEncryption
+ Voting.Protocol.Election: instance GHC.Classes.Eq Voting.Protocol.Election.Hash
+ Voting.Protocol.Election: instance GHC.Classes.Ord Voting.Protocol.Election.Hash
+ Voting.Protocol.Election: instance GHC.Generics.Generic (Voting.Protocol.Election.Answer q)
+ Voting.Protocol.Election: instance GHC.Generics.Generic (Voting.Protocol.Election.Ballot q)
+ Voting.Protocol.Election: instance GHC.Generics.Generic (Voting.Protocol.Election.DecryptionShare q)
+ Voting.Protocol.Election: instance GHC.Generics.Generic (Voting.Protocol.Election.DisjProof q)
+ Voting.Protocol.Election: instance GHC.Generics.Generic (Voting.Protocol.Election.Election q)
+ Voting.Protocol.Election: instance GHC.Generics.Generic (Voting.Protocol.Election.Encryption q)
+ Voting.Protocol.Election: instance GHC.Generics.Generic (Voting.Protocol.Election.Proof q)
+ Voting.Protocol.Election: instance GHC.Generics.Generic (Voting.Protocol.Election.Question q)
+ Voting.Protocol.Election: instance GHC.Generics.Generic (Voting.Protocol.Election.Signature q)
+ Voting.Protocol.Election: instance GHC.Generics.Generic (Voting.Protocol.Election.Tally q)
+ Voting.Protocol.Election: instance GHC.Generics.Generic Voting.Protocol.Election.ErrorAnswer
+ Voting.Protocol.Election: instance GHC.Generics.Generic Voting.Protocol.Election.ErrorBallot
+ Voting.Protocol.Election: instance GHC.Generics.Generic Voting.Protocol.Election.ErrorDecryptionShare
+ Voting.Protocol.Election: instance GHC.Generics.Generic Voting.Protocol.Election.Hash
+ Voting.Protocol.Election: instance GHC.Show.Show (Voting.Protocol.Election.Answer q)
+ Voting.Protocol.Election: instance GHC.Show.Show (Voting.Protocol.Election.DecryptionShare q)
+ Voting.Protocol.Election: instance GHC.Show.Show (Voting.Protocol.Election.DisjProof q)
+ Voting.Protocol.Election: instance GHC.Show.Show (Voting.Protocol.Election.Election q)
+ Voting.Protocol.Election: instance GHC.Show.Show (Voting.Protocol.Election.Encryption q)
+ Voting.Protocol.Election: instance GHC.Show.Show (Voting.Protocol.Election.Proof q)
+ Voting.Protocol.Election: instance GHC.Show.Show (Voting.Protocol.Election.Question q)
+ Voting.Protocol.Election: instance GHC.Show.Show (Voting.Protocol.Election.Tally q)
+ Voting.Protocol.Election: instance GHC.Show.Show Voting.Protocol.Election.ErrorAnswer
+ Voting.Protocol.Election: instance GHC.Show.Show Voting.Protocol.Election.ErrorBallot
+ Voting.Protocol.Election: instance GHC.Show.Show Voting.Protocol.Election.ErrorDecryptionShare
+ Voting.Protocol.Election: instance GHC.Show.Show Voting.Protocol.Election.ErrorVerifyEncryption
+ Voting.Protocol.Election: instance GHC.Show.Show Voting.Protocol.Election.Hash
+ Voting.Protocol.Election: instance Voting.Protocol.Arithmetic.SubGroup q => Voting.Protocol.Arithmetic.Additive (Voting.Protocol.Election.Encryption q)
+ Voting.Protocol.Election: intervalDisjunctions :: SubGroup q => Opinion q -> Opinion q -> [Disjunction q]
+ Voting.Protocol.Election: newtype DisjProof q
+ Voting.Protocol.Election: newtype Hash
+ Voting.Protocol.Election: newtype ZKP
+ Voting.Protocol.Election: prove :: Monad m => RandomGen r => SubGroup q => Functor list => E q -> list (G q) -> Oracle list q -> StateT r m (Proof q)
+ Voting.Protocol.Election: proveDecryptionFactor :: Monad m => SubGroup q => RandomGen r => SecretKey q -> Encryption q -> StateT r m (DecryptionFactor q, Proof q)
+ Voting.Protocol.Election: proveDecryptionShare :: Monad m => SubGroup q => RandomGen r => SecretKey q -> [[Encryption q]] -> StateT r m (DecryptionShare q)
+ Voting.Protocol.Election: proveEncryption :: Monad m => RandomGen r => SubGroup q => PublicKey q -> ZKP -> ([Disjunction q], [Disjunction q]) -> (EncryptionNonce q, Encryption q) -> StateT r m (DisjProof q)
+ Voting.Protocol.Election: proveTally :: Monad m => SubGroup q => [[Encryption q]] -> [DecryptionShare q] -> DecryptionShareCombinator q -> Except ErrorDecryptionShare (Tally q)
+ Voting.Protocol.Election: signatureCommitments :: SubGroup q => ZKP -> Commitment q -> ByteString
+ Voting.Protocol.Election: signatureStatement :: Foldable f => SubGroup q => f (Answer q) -> [G q]
+ Voting.Protocol.Election: type Challenge = E
+ Voting.Protocol.Election: type Commitment = G
+ Voting.Protocol.Election: type DecryptionFactor = G
+ Voting.Protocol.Election: type DecryptionShareCombinator q = [DecryptionShare q] -> Except ErrorDecryptionShare [[DecryptionFactor q]]
+ Voting.Protocol.Election: type Disjunction = G
+ Voting.Protocol.Election: type EncryptionNonce = E
+ Voting.Protocol.Election: type Opinion = E
+ Voting.Protocol.Election: type Oracle list q = list (Commitment q) -> Challenge q
+ Voting.Protocol.Election: verifyAnswer :: SubGroup q => PublicKey q -> ZKP -> Question q -> Answer q -> Bool
+ Voting.Protocol.Election: verifyBallot :: SubGroup q => Election q -> Ballot q -> Bool
+ Voting.Protocol.Election: verifyDecryptionShare :: Monad m => SubGroup q => [[Encryption q]] -> PublicKey q -> DecryptionShare q -> ExceptT ErrorDecryptionShare m ()
+ Voting.Protocol.Election: verifyEncryption :: Monad m => SubGroup q => PublicKey q -> ZKP -> [Disjunction q] -> (Encryption q, DisjProof q) -> ExceptT ErrorVerifyEncryption m Bool
+ Voting.Protocol.Election: verifyTally :: Monad m => SubGroup q => DecryptionShareCombinator q -> Tally q -> Except ErrorDecryptionShare ()
+ Voting.Protocol.Trustees.All: ErrorTrusteePublicKey_Wrong :: ErrorTrusteePublicKey
+ Voting.Protocol.Trustees.All: TrusteePublicKey :: PublicKey q -> Proof q -> TrusteePublicKey q
+ Voting.Protocol.Trustees.All: [trustee_PublicKey] :: TrusteePublicKey q -> PublicKey q
+ Voting.Protocol.Trustees.All: [trustee_SecretKeyProof] :: TrusteePublicKey q -> Proof q
+ Voting.Protocol.Trustees.All: combineDecryptionShares :: SubGroup q => [[Encryption q]] -> [PublicKey q] -> DecryptionShareCombinator q
+ Voting.Protocol.Trustees.All: data ErrorTrusteePublicKey
+ Voting.Protocol.Trustees.All: data TrusteePublicKey q
+ Voting.Protocol.Trustees.All: electionPublicKey :: SubGroup q => [TrusteePublicKey q] -> PublicKey q
+ Voting.Protocol.Trustees.All: instance GHC.Classes.Eq (Voting.Protocol.Trustees.All.TrusteePublicKey q)
+ Voting.Protocol.Trustees.All: instance GHC.Classes.Eq Voting.Protocol.Trustees.All.ErrorTrusteePublicKey
+ Voting.Protocol.Trustees.All: instance GHC.Show.Show (Voting.Protocol.Trustees.All.TrusteePublicKey q)
+ Voting.Protocol.Trustees.All: instance GHC.Show.Show Voting.Protocol.Trustees.All.ErrorTrusteePublicKey
+ Voting.Protocol.Trustees.All: proveTrusteePublicKey :: Monad m => RandomGen r => SubGroup q => SecretKey q -> StateT r m (TrusteePublicKey q)
+ Voting.Protocol.Trustees.All: randomSecretKey :: Monad m => RandomGen r => SubGroup q => StateT r m (SecretKey q)
+ Voting.Protocol.Trustees.All: trusteePublicKeyStatement :: PublicKey q -> ByteString
+ Voting.Protocol.Trustees.All: verifyTrusteePublicKey :: Monad m => SubGroup q => TrusteePublicKey q -> ExceptT ErrorTrusteePublicKey m ()
+ Voting.Protocol.Utils: isoZipWith :: (a -> b -> c) -> [a] -> [b] -> Maybe [c]
+ Voting.Protocol.Utils: isoZipWith3 :: (a -> b -> c -> d) -> [a] -> [b] -> [c] -> Maybe [d]
+ Voting.Protocol.Utils: isoZipWith3M :: Applicative m => m () -> (a -> b -> c -> m d) -> [a] -> [b] -> [c] -> m [d]
+ Voting.Protocol.Utils: isoZipWith3M_ :: Applicative m => m () -> (a -> b -> c -> m d) -> [a] -> [b] -> [c] -> m ()
+ Voting.Protocol.Utils: isoZipWithM :: Applicative m => m () -> (a -> b -> m c) -> [a] -> [b] -> m [c]
+ Voting.Protocol.Utils: isoZipWithM_ :: Applicative m => m () -> (a -> b -> m c) -> [a] -> [b] -> m ()
Files
- Protocol/Arithmetic.hs +0/−326
- Protocol/Credential.hs +0/−133
- Protocol/Election.hs +0/−597
- README.md +57/−0
- benchmarks/Election.hs +102/−0
- benchmarks/Main.hs +11/−0
- benchmarks/Utils.hs +60/−0
- hjugement-protocol.cabal +63/−53
- src/Voting/Protocol.hs +11/−0
- src/Voting/Protocol/Arithmetic.hs +337/−0
- src/Voting/Protocol/Credential.hs +137/−0
- src/Voting/Protocol/Election.hs +670/−0
- src/Voting/Protocol/Trustees.hs +5/−0
- src/Voting/Protocol/Trustees/All.hs +92/−0
- src/Voting/Protocol/Utils.hs +63/−0
- test/HUnit.hs +0/−13
- test/HUnit/Arithmetic.hs +0/−40
- test/HUnit/Credential.hs +0/−53
- test/HUnit/Election.hs +0/−111
- test/HUnit/Utils.hs +0/−37
- test/Main.hs +0/−15
- tests/HUnit.hs +13/−0
- tests/HUnit/Arithmetic.hs +41/−0
- tests/HUnit/Credential.hs +54/−0
- tests/HUnit/Election.hs +126/−0
- tests/Main.hs +15/−0
- tests/QuickCheck.hs +11/−0
- tests/QuickCheck/Election.hs +151/−0
- tests/QuickCheck/Trustee.hs +37/−0
- tests/Utils.hs +83/−0
− Protocol/Arithmetic.hs
@@ -1,326 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}-module Protocol.Arithmetic- ( module Protocol.Arithmetic- , Natural- ) where--import Control.Arrow (first)-import Control.Monad (Monad(..))-import Data.Bits-import Data.Bool-import Data.Eq (Eq(..))-import Data.Foldable (Foldable, foldl')-import Data.Function (($), (.))-import Data.Functor ((<$>))-import Data.Int (Int)-import Data.Maybe (Maybe(..))-import Data.Ord (Ord(..))-import Data.Semigroup (Semigroup(..))-import Data.String (IsString(..))-import Numeric.Natural (Natural)-import Prelude (Integer, Integral(..), fromIntegral, Enum(..))-import Text.Show (Show(..))-import qualified Control.Monad.Trans.State.Strict as S-import qualified Crypto.Hash as Crypto-import qualified Data.ByteArray as ByteArray-import qualified Data.ByteString as BS-import qualified Data.List as List-import qualified Prelude as Num-import qualified System.Random as Random---- * Type 'F'--- | The type of the elements of a 'PrimeField'.------ A field must satisfy the following properties:------ * @(f, ('+'), 'zero')@ forms an abelian group,--- called the 'Additive' group of 'f'.------ * @('NonNull' f, ('*'), 'one')@ forms an abelian group,--- called the 'Multiplicative' group of 'f'.------ * ('*') is associative:--- @(a'*'b)'*'c == a'*'(b'*'c)@ and--- @a'*'(b'*'c) == (a'*'b)'*'c@.------ * ('*') and ('+') are both commutative:--- @a'*'b == b'*'a@ and--- @a'+'b == b'+'a@------ * ('*') and ('+') are both left and right distributive:--- @a'*'(b'+'c) == (a'*'b) '+' (a'*'c)@ and--- @(a'+'b)'*'c == (a'*'c) '+' (b'*'c)@------ The 'Natural' is always within @[0..'fieldCharac'-1]@.-newtype F p = F { unF :: Natural }- deriving (Eq,Ord,Show)--instance PrimeField p => FromNatural (F p) where- fromNatural i = F (abs (i `mod` fieldCharac @p))- where abs x | x < 0 = x + fieldCharac @p- | otherwise = x-instance ToNatural (F p) where- nat = unF--instance PrimeField p => Additive (F p) where- zero = F 0- F x + F y = F ((x + y) `mod` fieldCharac @p)-instance PrimeField p => Negable (F p) where- neg (F x) | x == 0 = zero- | otherwise = F (fromIntegral (Num.negate (toInteger x) + toInteger (fieldCharac @p)))-instance PrimeField p => Multiplicative (F p) where- one = F 1- -- | Because 'fieldCharac' is prime,- -- all elements of the field are invertible modulo 'fieldCharac'.- F x * F y = F ((x * y) `mod` fieldCharac @p)-instance PrimeField p => Random.Random (F p) where- randomR (F lo, F hi) =- first (F . fromIntegral) .- Random.randomR- ( 0`max`toInteger lo- , toInteger hi`min`(toInteger (fieldCharac @p) - 1))- random = first (F . fromIntegral) . Random.randomR (0, toInteger (fieldCharac @p) - 1)---- ** Class 'PrimeField'--- | Parameter for a prime field.-class PrimeField p where- -- | The prime number characteristic of a 'PrimeField'.- --- -- ElGamal's hardness to decrypt requires a large prime number- -- to form the 'Multiplicative' 'SubGroup'.- fieldCharac :: Natural---- ** Class 'Additive'-class Additive a where- zero :: a- (+) :: a -> a -> a; infixl 6 +- sum :: Foldable f => f a -> a- sum = foldl' (+) zero-instance Additive Natural where- zero = 0- (+) = (Num.+)-instance Additive Integer where- zero = 0- (+) = (Num.+)-instance Additive Int where- zero = 0- (+) = (Num.+)---- *** Class 'Negable'-class Additive a => Negable a where- neg :: a -> a- (-) :: a -> a -> a; infixl 6 -- x-y = x + neg y-instance Negable Integer where- neg = Num.negate-instance Negable Int where- neg = Num.negate---- ** Class 'Multiplicative'-class Multiplicative a where- one :: a- (*) :: a -> a -> a; infixl 7 *-instance Multiplicative Natural where- one = 1- (*) = (Num.*)-instance Multiplicative Integer where- one = 1- (*) = (Num.*)-instance Multiplicative Int where- one = 1- (*) = (Num.*)---- ** Class 'Invertible'-class Multiplicative a => Invertible a where- inv :: a -> a- (/) :: a -> a -> a; infixl 7 /- x/y = x * inv y---- * 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)--instance PrimeField (P q) => FromNatural (G q) where- fromNatural = G . fromNatural-instance ToNatural (G q) where- nat = unF . unG--instance (SubGroup q, Multiplicative (F (P q))) => Multiplicative (G q) where- one = G one- G x * G y = G (x * y)-instance (SubGroup q, Multiplicative (F (P q))) => Invertible (G q) where- -- | NOTE: add 'groupOrder' so the exponent given to (^) is positive.- inv = (^ E (neg one + groupOrder @q))---- ** Class 'SubGroup'--- | A 'SubGroup' of a 'Multiplicative' group of a 'PrimeField'.--- Used for signing (Schnorr) and encrypting (ElGamal).-class- ( PrimeField (P q)- , Multiplicative (F (P q))- ) => SubGroup q where- -- | Setting 'q' determines 'p', equals to @'P' q@.- type P q :: *- -- | A generator of the 'SubGroup'.- -- NOTE: since @F p@ is a 'PrimeField',- -- the 'Multiplicative' 'SubGroup' is cyclic,- -- and there are phi('fieldCharac'-1) many choices for the generator of the group,- -- where phi is the Euler totient function.- groupGen :: G q- -- | The order of the 'SubGroup'.- --- -- WARNING: 'groupOrder' MUST be a prime number dividing @('fieldCharac'-1)@- -- to ensure that ElGamal is secure in terms of the DDH assumption.- groupOrder :: F (P q)- - -- | 'groupGenInverses' returns the infinite list- -- of 'inv'erse powers of 'groupGen':- -- @['groupGen' '^' 'neg' i | i <- [0..]]@,- -- but by computing each value from the previous one.- --- -- NOTE: 'groupGenInverses' is in the 'SubGroup' class in order to keep- -- computed terms in memory across calls to 'groupGenInverses'.- --- -- Used by 'intervalDisjunctions'.- groupGenInverses :: [G q]- groupGenInverses = go one- where- go g = g : go (g * invGen)- invGen = inv 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',--- with a comma (",") intercalated between them.------ NOTE: to avoid any collision when the 'hash' function is used in different contexts,--- a message 'gs' is actually prefixed by a 'bs' indicating the context.------ Used by 'proveEncryption' and 'verifyEncryption',--- where the 'bs' usually contains the 'statement' to be proven,--- and the 'gs' contains the 'commitments'.-hash ::- SubGroup q =>- BS.ByteString -> [G q] -> E q-hash bs gs =- let s = bs <> BS.intercalate (fromString ",") (bytesNat <$> gs) in- let h = ByteArray.convert (Crypto.hashWith Crypto.SHA256 s) in- fromNatural (BS.foldl' (\acc b -> acc`shiftL`3 + fromIntegral b) (0::Natural) h)---- * Type 'E'--- | 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)--instance SubGroup q => FromNatural (E q) where- fromNatural i = E (F (abs (i `mod` unF (groupOrder @q))))- where abs x | x < 0 = x + unF (groupOrder @q)- | otherwise = x-instance ToNatural (E q) where- nat = unF . unE--instance (SubGroup q, Additive (F (P q))) => Additive (E q) where- zero = E zero- E (F x) + E (F y) = E (F ((x + y) `mod` unF (groupOrder @q)))-instance (SubGroup q, Negable (F (P q))) => Negable (E q) where- neg (E (F x)) | x == 0 = zero- | otherwise = E (F (fromIntegral ( neg (toInteger x)- + toInteger (unF (groupOrder @q)) )))-instance (SubGroup q, Multiplicative (F (P q))) => Multiplicative (E q) where- one = E one- E (F x) * E (F y) = E (F ((x * y) `mod` unF (groupOrder @q)))-instance SubGroup q => Random.Random (E q) where- randomR (E (F lo), E (F hi)) =- first (E . F . fromIntegral) .- Random.randomR- ( 0`max`toInteger lo- , toInteger hi`min`(toInteger (unF (groupOrder @q)) - 1) )- random =- first (E . F . fromIntegral) .- Random.randomR (0, toInteger (unF (groupOrder @q)) - 1)-instance SubGroup q => Enum (E q) where- toEnum = fromNatural . fromIntegral- fromEnum = fromIntegral . nat- enumFromTo lo hi = List.unfoldr- (\i -> if i<=hi then Just (i, i+one) else Nothing) lo--infixr 8 ^--- | @(b '^' e)@ returns the modular exponentiation of base 'b' by exponent 'e'.-(^) :: SubGroup q => G q -> E q -> G q-(^) b (E (F e))- | e == zero = one- | otherwise = t * (b*b) ^ E (F (e`shiftR`1))- where- t | testBit e 0 = b- | otherwise = one---- * Type 'RandomGen'-type RandomGen = Random.RandomGen---- | @('randomR' i)@ returns a random integer in @[0..i-1]@.-randomR ::- Monad m =>- RandomGen r =>- Random.Random i =>- Negable i =>- Multiplicative i =>- i -> S.StateT r m i-randomR i = S.StateT $ return . Random.randomR (zero, i-one)---- | @('random')@ returns a random integer--- in the range determined by its type.-random ::- Monad m =>- RandomGen r =>- Random.Random i =>- Negable i =>- Multiplicative i =>- S.StateT r m i-random = S.StateT $ return . Random.random--instance Random.Random Natural where- randomR (mini,maxi) =- first (fromIntegral::Integer -> Natural) .- Random.randomR (fromIntegral mini, fromIntegral maxi)- random = first (fromIntegral::Integer -> Natural) . Random.random---- * Groups---- ** Type 'WeakParams'--- | Weak parameters for debugging purposes only.-data WeakParams-instance PrimeField WeakParams where- fieldCharac = 263-instance SubGroup WeakParams where- type P WeakParams = WeakParams- groupGen = G (F 2)- groupOrder = F 131---- ** Type 'BeleniosParams'--- | Parameters used in Belenios.--- A 2048-bit 'fieldCharac' of a 'PrimeField',--- with a 256-bit 'groupOrder' for a 'Multiplicative' 'SubGroup'--- generated by 'groupGen'.-data BeleniosParams-instance PrimeField BeleniosParams where- fieldCharac = 20694785691422546401013643657505008064922989295751104097100884787057374219242717401922237254497684338129066633138078958404960054389636289796393038773905722803605973749427671376777618898589872735865049081167099310535867780980030790491654063777173764198678527273474476341835600035698305193144284561701911000786737307333564123971732897913240474578834468260652327974647951137672658693582180046317922073668860052627186363386088796882120769432366149491002923444346373222145884100586421050242120365433561201320481118852408731077014151666200162313177169372189248078507711827842317498073276598828825169183103125680162072880719-instance SubGroup BeleniosParams where- type P BeleniosParams = BeleniosParams- groupGen = G (F 2402352677501852209227687703532399932712287657378364916510075318787663274146353219320285676155269678799694668298749389095083896573425601900601068477164491735474137283104610458681314511781646755400527402889846139864532661215055797097162016168270312886432456663834863635782106154918419982534315189740658186868651151358576410138882215396016043228843603930989333662772848406593138406010231675095763777982665103606822406635076697764025346253773085133173495194248967754052573659049492477631475991575198775177711481490920456600205478127054728238140972518639858334115700568353695553423781475582491896050296680037745308460627)- groupOrder = F 78571733251071885079927659812671450121821421258408794611510081919805623223441---- * Conversions---- ** Class 'FromNatural'-class FromNatural a where- fromNatural :: Natural -> a---- ** Class 'ToNatural'-class ToNatural a where- nat :: a -> Natural---- | @('bytesNat' x)@ returns the serialization of 'x'.-bytesNat :: ToNatural n => n -> BS.ByteString-bytesNat = fromString . show . nat
− Protocol/Credential.hs
@@ -1,133 +0,0 @@-module Protocol.Credential where--import Control.Monad (Monad(..), replicateM)-import Data.Bits-import Data.Bool-import Data.Char (Char)-import Data.Either (Either(..))-import Data.Eq (Eq(..))-import Data.Function (($))-import Data.Functor ((<$>))-import Data.Int (Int)-import Data.Maybe (maybe)-import Data.Ord (Ord(..))-import Data.Text (Text)-import Numeric.Natural (Natural)-import Prelude (Integral(..), fromIntegral, div)-import Text.Show (Show)-import qualified Control.Monad.Trans.State.Strict as S-import qualified Crypto.KDF.PBKDF2 as Crypto-import qualified Data.ByteArray as ByteArray-import qualified Data.ByteString as BS-import qualified Data.Char as Char-import qualified Data.List as List-import qualified Data.Text as Text-import qualified Data.Text.Encoding as Text-import qualified System.Random as Random--import Protocol.Arithmetic---- * Type 'Credential'--- | A 'Credential' is a word of @('tokenLength'+1 '==' 15)@-characters--- from a base alphabet of (@'tokenBase' '==' 58)@ characters:--- "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"--- (beware the absence of "0", \"O", \"I", and "l").--- The last character is a checksum.--- The entropy is: @('tokenLength' * log 'tokenBase' / log 2) '==' 82.01… bits@.-newtype Credential = Credential Text- deriving (Eq, Show)--credentialAlphabet :: [Char] -- TODO: make this an array-credentialAlphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"-tokenBase :: Int-tokenBase = List.length credentialAlphabet-tokenLength ::Int-tokenLength = 14---- | @'randomCredential'@ generates a random 'Credential'.-randomCredential ::- Monad m =>- Random.RandomGen r =>- S.StateT r m Credential-randomCredential = do- rs <- replicateM tokenLength (randomR (fromIntegral tokenBase))- let (tot, cs) = List.foldl' (\(acc,ds) d ->- ( acc * tokenBase + d- , charOfDigit d : ds )- ) (zero::Int, []) rs- let checksum = (neg tot + 53) `mod` 53 -- NOTE: why 53 and not 'tokenBase' ?- return $ Credential $ Text.reverse $ Text.pack (charOfDigit checksum:cs)- where- charOfDigit = (credentialAlphabet List.!!)---- | @'readCredential'@ reads and check the well-formedness of a 'Credential'--- from raw 'Text'.-readCredential :: Text -> Either CredentialError Credential-readCredential s- | Text.length s /= tokenLength + 1 = Left CredentialError_Length- | otherwise = do- tot <- Text.foldl'- (\acc c -> acc >>= \a -> ((a * tokenBase) +) <$> digitOfChar c)- (Right (zero::Int))- (Text.init s)- checksum <- digitOfChar (Text.last s)- if (tot + checksum) `mod` 53 == 0- then Right (Credential s)- else Left CredentialError_Checksum- where- digitOfChar c =- maybe (Left $ CredentialError_BadChar c) Right $- List.elemIndex c credentialAlphabet---- ** Type 'CredentialError'-data CredentialError- = CredentialError_BadChar Char.Char- | CredentialError_Checksum- | CredentialError_Length- deriving (Eq, Show)---- ** Type 'UUID'-newtype UUID = UUID Text- deriving (Eq,Ord,Show)---- | @'randomUUID'@ generates a random 'UUID'.-randomUUID ::- Monad m =>- Random.RandomGen r =>- S.StateT r m UUID-randomUUID = do- rs <- replicateM tokenLength (randomR (fromIntegral tokenBase))- return $ UUID $ Text.pack $ charOfDigit <$> rs- where- charOfDigit = (credentialAlphabet List.!!)---- ** Type 'SecretKey'-type SecretKey = E---- | @('secretKey' 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) =- fromNatural $ BS.foldl'- (\acc b -> acc`shiftL`3 + fromIntegral b)- (0::Natural)- (ByteArray.convert deriv)- where- deriv :: BS.ByteString- deriv =- Crypto.fastPBKDF2_SHA256- Crypto.Parameters- { Crypto.iterCounts = 1000- , Crypto.outputLength = 256 `div` 8- }- (Text.encodeUtf8 cred)- (Text.encodeUtf8 uuid)---- ** Type 'PublicKey'-type PublicKey = G---- | @('publicKey' secKey)@ returns the 'PublicKey'--- derived from given 'SecretKey'.-publicKey :: SubGroup q => SecretKey q -> PublicKey q-publicKey = (groupGen ^)
− Protocol/Election.hs
@@ -1,597 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}-module Protocol.Election where--import Control.Monad (Monad(..), join, mapM, zipWithM)-import Control.Monad.Morph (MFunctor(..))-import Control.Monad.Trans.Class (MonadTrans(..))-import Data.Bool-import Data.Either (either)-import Data.Eq (Eq(..))-import Data.Foldable (Foldable, foldMap, and)-import Data.Function (($), id, const)-import Data.Functor (Functor, (<$>))-import Data.Functor.Identity (Identity(..))-import Data.Maybe (Maybe(..), fromMaybe)-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 Numeric.Natural (Natural)-import Prelude (fromIntegral)-import Text.Show (Show(..))-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--import Protocol.Arithmetic-import Protocol.Credential---- * Type 'Encryption'--- | ElGamal-like encryption.--- Its security relies on the /Discrete Logarithm problem/.------ Because ('groupGen' '^'encNonce '^'secKey '==' 'groupGen' '^'secKey '^'encNonce),--- knowing @secKey@, one can divide 'encryption_vault' by @('encryption_nonce' '^'secKey)@--- to decipher @('groupGen' '^'clear)@, then the @clear@ text must be small to be decryptable,--- because it is encrypted as a power of 'groupGen' (hence the "-like" in "ElGamal-like")--- to enable the additive homomorphism.------ NOTE: Since @('encryption_vault' '*' 'encryption_nonce' '==' 'encryption_nonce' '^' (secKey '+' clear))@,--- then: @(logBase 'encryption_nonce' ('encryption_vault' '*' 'encryption_nonce') '==' secKey '+' clear)@.-data Encryption q = Encryption- { encryption_nonce :: G q- -- ^ Public part of the randomness 'encNonce' used to 'encrypt' the 'clear' text,- -- equal to @('groupGen' '^'encNonce)@- , encryption_vault :: G q- -- ^ Encrypted 'clear' text, equal to @('pubKey' '^'r '*' 'groupGen' '^'clear)@- } deriving (Eq,Show)---- | Additive homomorphism.--- Using the fact that: @'groupGen' '^'x '*' 'groupGen' '^'y '==' 'groupGen' '^'(x'+'y)@.-instance SubGroup q => Additive (Encryption q) where- zero = Encryption one one- x+y = Encryption- (encryption_nonce x * encryption_nonce y)- (encryption_vault x * encryption_vault y)---- *** Type 'EncryptionNonce'-type EncryptionNonce = E---- | @('encrypt' pubKey clear)@ returns an ElGamal-like 'Encryption'.------ WARNING: the secret encryption nonce (@encNonce@)--- is returned alongside the 'Encryption'--- in order to 'prove' the validity of the encrypted 'clear' text in 'proveEncryption',--- but this secret @encNonce@ MUST be forgotten after that,--- as it may be used to decipher the 'Encryption'--- without the secret key associated with 'pubKey'.-encrypt ::- Monad m => RandomGen r => SubGroup q =>- PublicKey q -> E q ->- S.StateT r m (EncryptionNonce q, Encryption q)-encrypt pubKey clear = do- encNonce <- random- -- NOTE: preserve the 'encNonce' for 'prove' in 'proveEncryption'.- return $ (encNonce,)- Encryption- { encryption_nonce = groupGen^encNonce- , encryption_vault = pubKey ^encNonce * groupGen^clear- }---- * Type 'Proof'--- | 'Proof' of knowledge of a discrete logarithm:--- @(secret == logBase base (base^secret))@.-data Proof q = Proof- { proof_challenge :: Challenge q- -- ^ 'Challenge' sent by the verifier to the prover- -- to ensure that the prover really has knowledge- -- of the secret and is not replaying.- -- Actually, 'proof_challenge' is not sent to the prover,- -- but derived from the prover's 'Commitment's and statements- -- with a collision resistant 'hash'.- -- Hence the prover cannot chose the 'proof_challenge' to his/her liking.- , proof_response :: E q- -- ^ A discrete logarithm sent by the prover to the verifier,- -- as a response to 'proof_challenge'.- --- -- If the verifier observes that @('proof_challenge' '==' 'hash' statement [commitment])@- -- where:- --- -- * @statement@ is a serialization of a tag, 'base' and 'basePowSec',- -- * @(commitment '==' 'commit' proof base basePowSec '=='- -- base '^' 'proof_response' '*' basePowSec '^' 'proof_challenge')@,- -- * and @(basePowSec '==' base'^'sec)@,- --- -- then, with overwhelming probability due to the 'hash' function:- -- @(commitment '==' base'^'nonce)@.- -- Therefore by expanding 'commitment':- -- @('proof_response' '==' logBase base (base'^'nonce) '-' logBase basePowSec (basePowSec '^' 'proof_challenge'))@,- -- which means that the prover must have known 'nonce' and 'sec'- -- to compute 'proof_response' efficiently with:- -- @('proof_response' '==' nonce '-' sec '*' 'proof_challenge')@,- --- -- The 'nonce' is introduced to ensure each 'prove' does not reveal- -- any information regarding the prover's secret 'sec',- -- by being randomly chosen by the prover.- } deriving (Eq,Show)---- ** Type 'ZKP'--- | Zero-knowledge proof------ DOC: Mihir Bellare and Phillip Rogaway. Random oracles are practical:--- A paradigm for designing efficient protocols. In ACM-CCS’93, 1993.------ DOC: Pierrick Gaudry. <https://hal.inria.fr/hal-01576379 Some ZK security proofs for Belenios>, 2017.-newtype ZKP = ZKP BS.ByteString---- ** Type 'Challenge'-type Challenge = E---- ** Type 'Oracle'--- An 'Oracle' returns the 'Challenge' of the 'Commitment's--- by 'hash'ing them (eventually with other 'Commitment's).------ Used in 'prove' it enables a Fiat-Shamir transformation--- of an /interactive zero-knowledge/ (IZK) proof--- into a /non-interactive zero-knowledge/ (NIZK) proof.--- That is to say that the verifier does not have--- to send a 'Challenge' to the prover.--- Indeed, the prover now handles the 'Challenge'--- which becomes a (collision resistant) 'hash'--- of the prover's commitments (and statements to be a stronger proof).-type Oracle list q = list (Commitment q) -> Challenge q---- | @('prove' sec commitBases oracle)@--- returns a 'Proof' that @sec@ is known.------ The 'Oracle' is given the 'commitBases'--- raised to the power of the secret nonce of the 'Proof',--- as those are the 'commitBases' that the verifier will obtain--- when composing the 'proof_challenge' and 'proof_response' together--- (in '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).------ NOTE: a 'random' @nonce@ is used to ensure each 'prove'--- does not reveal any information regarding the secret 'sec'.-prove ::- Monad m => RandomGen r => SubGroup q => Functor list =>- E q -> list (Commitment q) -> Oracle list q -> S.StateT r m (Proof q)-prove sec commitBases oracle = do- nonce <- random- let proof_challenge = oracle $ (^ nonce) <$> commitBases- return Proof- { proof_challenge- , proof_response = nonce - sec*proof_challenge- }---- ** Type 'Commitment'-type Commitment = G---- | @('commit' proof base basePowSec)@ returns a 'Commitment'--- from the given 'Proof' with the knowledge of the verifier.-commit :: SubGroup q => Proof q -> G q -> G q -> Commitment q-commit Proof{..} base basePowSec =- base^proof_response *- basePowSec^proof_challenge- -- NOTE: Contrary to some textbook presentations,- -- @('*')@ is used instead of @('/')@ to avoid the performance cost- -- of a modular exponentiation @('^' ('groupOrder' '-' 'one'))@,- -- this is compensated by using @('-')@ instead of @('+')@ in 'prove'.-{-# INLINE commit #-}---- * Type 'Disjunction'--- | A 'Disjunction' is an 'inv'ersed @('groupGen' '^'opinion)@--- it's used in 'proveEncryption' to generate a 'Proof'--- that an 'encryption_vault' contains a given @('groupGen' '^'opinion)@,-type Disjunction = G--booleanDisjunctions :: SubGroup q => [Disjunction q]-booleanDisjunctions = List.take 2 groupGenInverses--intervalDisjunctions :: SubGroup q => Opinion q -> Opinion q -> [Disjunction q]-intervalDisjunctions mini maxi =- List.genericTake (fromMaybe 0 $ (nat maxi + 1)`minusNaturalMaybe`nat mini) $- List.genericDrop (nat mini) $- groupGenInverses---- ** Type 'Opinion'--- | Index of a 'Disjunction' within a list of them.--- It is encrypted as an 'E'xponent by 'encrypt'.-type Opinion = E---- ** Type 'DisjProof'--- | A list of 'Proof's to prove that the 'Opinion' within an 'Encryption'--- is indexing a 'Disjunction' within a list of them,--- without revealing which 'Opinion' it is.-newtype DisjProof q = DisjProof [Proof q]- deriving (Eq,Show)---- | @('proveEncryption' elecPubKey voterZKP (prevDisjs,nextDisjs) (encNonce,enc))@--- returns a 'DisjProof' that 'enc' 'encrypt's--- the 'Disjunction's between 'prevDisjs' and 'nextDisjs'.------ A /NIZK Disjunctive Chaum Pedersen Logarithm Equality/ is used.-proveEncryption ::- forall m r q.- Monad m => RandomGen r => SubGroup q =>- PublicKey q -> ZKP ->- ([Disjunction q],[Disjunction q]) ->- (EncryptionNonce q, Encryption q) ->- S.StateT r m (DisjProof q)-proveEncryption elecPubKey voterZKP (prevDisjs,nextDisjs) (encNonce,enc) = do- -- Fake proofs for all values except the correct one.- prevFakes <- fakeProof `mapM` prevDisjs- nextFakes <- fakeProof `mapM` nextDisjs- let prevProofs = fst <$> prevFakes- let nextProofs = fst <$> nextFakes- let challengeSum =- sum (proof_challenge <$> prevProofs) +- sum (proof_challenge <$> nextProofs)- let statement = encryptionStatement voterZKP enc- correctProof <- prove encNonce [groupGen, elecPubKey] $- -- 'Oracle'- \correctCommitments ->- let commitments =- foldMap snd prevFakes <>- correctCommitments <>- foldMap snd nextFakes in- hash statement commitments - challengeSum- return $ DisjProof $ prevProofs <> (correctProof : nextProofs)- where- fakeProof :: Disjunction q -> S.StateT r m (Proof q, [Commitment q])- fakeProof disj = do- -- Returns 'Commitment's verifiables by the verifier,- -- but computed from random 'proof_challenge' and 'proof_response'- -- instead of correct ones.- proof_challenge <- random- proof_response <- random- let proof = Proof{..}- return (proof, encryptionCommitments elecPubKey enc (disj, proof))--verifyEncryption ::- Monad m =>- SubGroup q =>- PublicKey q -> ZKP ->- [Disjunction q] ->- (Encryption q, DisjProof q) ->- Exn.ExceptT ErrorValidateEncryption m Bool-verifyEncryption elecPubKey voterZKP disjs (enc, DisjProof proofs)- | List.length proofs /= List.length disjs =- Exn.throwE $ ErrorValidateEncryption_InvalidProofLength- (fromIntegral $ List.length proofs)- (fromIntegral $ List.length disjs)- | otherwise = return $ challengeSum == hash (encryptionStatement voterZKP enc) commitments- where- challengeSum = sum (proof_challenge <$> proofs)- commitments = foldMap- (encryptionCommitments elecPubKey enc)- (List.zip disjs proofs)---- ** Hashing-encryptionStatement :: SubGroup q => ZKP -> Encryption q -> BS.ByteString-encryptionStatement (ZKP voterZKP) Encryption{..} =- "prove|"<>voterZKP<>"|"- <> bytesNat encryption_nonce<>","- <> bytesNat encryption_vault<>"|"- -- NOTE: the commitment base 'elecPubKey' is notably absent here- -- despite it being used in 'encryptionCommitments',- -- maybe this is not necessary because it is already known- -- by every participant.---- | @('encryptionCommitments' elecPubKey enc (disj,proof))@--- returns the 'Commitment's with only the knowledge of the verifier.------ The 'Proof' comes from 'prove' of @fakeProof@ in 'proveEncryption'.-encryptionCommitments ::- SubGroup q =>- PublicKey q -> Encryption q ->- (Disjunction q, Proof q) -> [G q]-encryptionCommitments elecPubKey Encryption{..} (disj, proof) =- [ commit proof groupGen encryption_nonce- -- == groupGen ^ nonce if 'Proof' comes from 'prove'- , commit proof elecPubKey (encryption_vault*disj)- -- == elecPubKey ^ nonce if 'Proof' comes from 'prove'- -- and 'encryption_vault' encrypts (- logBase groupGen disj).- ]---- ** Type 'ErrorValidateEncryption'--- | Error raised by 'verifyEncryption'.-data ErrorValidateEncryption- = ErrorValidateEncryption_InvalidProofLength Natural Natural- -- ^ When the number of proofs is different than- -- the number of 'Disjunction's.- deriving (Eq,Show)---- * Type 'Question'-data Question q = Question- { question_text :: Text- , question_choices :: [Text]- , question_mini :: Opinion q- , question_maxi :: Opinion q- -- , question_blank :: Maybe Bool- } deriving (Eq, Show)---- * Type 'Answer'-data Answer q = Answer- { answer_opinions :: [(Encryption q, DisjProof q)]- -- ^ Encrypted 'Opinion' for each 'question_choices'- -- with a 'DisjProof' that they belong to [0,1].- , answer_sumProof :: DisjProof q- -- ^ Proofs that the sum of the 'Opinon's encrypted in 'answer_opinions'- -- is an element of @[mini..maxi]@.- -- , answer_blankProof ::- } deriving (Eq,Show)---- | @('encryptAnswer' elecPubKey zkp quest opinions)@--- returns an 'Answer' validable by 'verifyAnswer',--- unless an 'ErrorAnswer' is returned.-encryptAnswer ::- Monad m => RandomGen r => SubGroup q =>- PublicKey q -> ZKP ->- Question q -> [Bool] ->- S.StateT r (Exn.ExceptT ErrorAnswer m) (Answer q)-encryptAnswer elecPubKey zkp Question{..} opinionsBools- | not (question_mini <= opinionsSum && opinionsSum <= question_maxi) =- lift $ Exn.throwE $- ErrorAnswer_WrongSumOfOpinions- (nat opinionsSum)- (nat question_mini)- (nat question_maxi)- | List.length opinions /= List.length question_choices =- lift $ Exn.throwE $- ErrorAnswer_WrongNumberOfOpinions- (fromIntegral $ List.length opinions)- (fromIntegral $ List.length question_choices)- | otherwise = do- encryptions <- encrypt elecPubKey `mapM` opinions- individualProofs <- zipWithM- (\opinion -> proveEncryption elecPubKey zkp $- if opinion- then ([booleanDisjunctions List.!!0],[])- else ([],[booleanDisjunctions List.!!1]))- opinionsBools encryptions- sumProof <- proveEncryption elecPubKey zkp- ((List.tail <$>) $ List.genericSplitAt (nat (opinionsSum - question_mini)) $- intervalDisjunctions question_mini question_maxi)- ( sum (fst <$> encryptions) -- NOTE: sum the 'encNonce's- , sum (snd <$> encryptions) -- NOTE: sum the 'Encryption's- )- return $ Answer- { answer_opinions = List.zip- (snd <$> encryptions) -- NOTE: drop encNonce- individualProofs- , answer_sumProof = sumProof- }- where- opinionsSum = sum opinions- opinions = (\o -> if o then one else zero) <$> opinionsBools--verifyAnswer ::- SubGroup q =>- PublicKey q -> ZKP ->- Question q -> Answer q -> Bool-verifyAnswer elecPubKey zkp Question{..} Answer{..}- | List.length question_choices /= List.length answer_opinions = False- | otherwise = either (const False) id $ Exn.runExcept $ do- validOpinions <-- verifyEncryption elecPubKey zkp booleanDisjunctions- `traverse` answer_opinions- validSum <- verifyEncryption elecPubKey zkp- (intervalDisjunctions question_mini question_maxi)- ( sum (fst <$> answer_opinions)- , answer_sumProof )- return (and validOpinions && validSum)---- ** Type 'ErrorAnswer'--- | Error raised by 'encryptAnswer'.-data ErrorAnswer- = ErrorAnswer_WrongNumberOfOpinions Natural Natural- -- ^ When the number of opinions is different than- -- the number of choices ('question_choices').- | ErrorAnswer_WrongSumOfOpinions Natural Natural Natural- -- ^ When the sum of opinions is not within the bounds- -- of 'question_mini' and 'question_maxi'.- deriving (Eq,Show)---- * Type 'Election'-data Election q = Election- { election_name :: Text- , election_description :: Text- , election_publicKey :: PublicKey q- , election_questions :: [Question q]- , election_uuid :: UUID- , election_hash :: Hash -- TODO: serialize to JSON to calculate this- } deriving (Eq,Show)---- ** Type 'Hash'-newtype Hash = Hash Text- deriving (Eq,Ord,Show)---- * Type 'Ballot'-data Ballot q = Ballot- { ballot_answers :: [Answer q]- , ballot_signature :: Maybe (Signature q)- , ballot_election_uuid :: UUID- , ballot_election_hash :: Hash- }---- | @('encryptBallot' elec ('Just' secKey) 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'.-encryptBallot ::- Monad m => RandomGen r => SubGroup q =>- Election q -> Maybe (SecretKey q) -> [[Bool]] ->- S.StateT r (Exn.ExceptT ErrorBallot m) (Ballot q)-encryptBallot Election{..} secKeyMay opinionsByQuest- | List.length election_questions /= List.length opinionsByQuest =- lift $ Exn.throwE $- ErrorBallot_WrongNumberOfAnswers- (fromIntegral $ List.length opinionsByQuest)- (fromIntegral $ List.length election_questions)- | otherwise = do- let (voterKeys, voterZKP) =- case secKeyMay of- Nothing -> (Nothing, ZKP "")- Just secKey ->- ( Just (secKey, pubKey)- , ZKP (bytesNat pubKey) )- where pubKey = publicKey secKey- ballot_answers <-- hoist (Exn.withExceptT ErrorBallot_Answer) $- zipWithM (encryptAnswer election_publicKey voterZKP)- election_questions opinionsByQuest- ballot_signature <- case voterKeys of- Nothing -> return Nothing- Just (secKey, signature_publicKey) -> do- signature_proof <-- prove secKey (Identity groupGen) $- \(Identity commitment) ->- hash- -- NOTE: the order is unusual, the commitments are first- -- then comes the statement. Best guess is that- -- this is easier to code due to their respective types.- (signatureCommitments voterZKP commitment)- (signatureStatement ballot_answers)- return $ Just Signature{..}- return Ballot- { ballot_answers- , ballot_election_hash = election_hash- , ballot_election_uuid = election_uuid- , ballot_signature- }--verifyBallot :: SubGroup q => Election q -> Ballot q -> Bool-verifyBallot Election{..} Ballot{..} =- ballot_election_uuid == election_uuid &&- ballot_election_hash == election_hash &&- List.length election_questions == List.length ballot_answers &&- let (isValidSign, zkpSign) =- case ballot_signature of- Nothing -> (True, ZKP "")- Just Signature{..} ->- let zkp = ZKP (bytesNat signature_publicKey) in- (, zkp) $- proof_challenge signature_proof == hash- (signatureCommitments zkp (commit signature_proof groupGen signature_publicKey))- (signatureStatement ballot_answers)- in- and $ isValidSign :- List.zipWith (verifyAnswer election_publicKey zkpSign)- election_questions ballot_answers---- ** Type 'Signature'--- | Schnorr-like signature.------ Used by each voter to sign his/her encrypted 'Ballot'--- using his/her 'Credential',--- in order to avoid ballot stuffing.-data Signature q = Signature- { signature_publicKey :: PublicKey q- -- ^ Verification key.- , signature_proof :: Proof q- }---- *** Hashing---- | @('signatureStatement' answers)@--- returns the encrypted material to be signed:--- all the 'encryption_nonce's and 'encryption_vault's of the given @answers@.-signatureStatement :: Foldable f => SubGroup q => f (Answer q) -> [G q]-signatureStatement =- foldMap $ \Answer{..} ->- (`foldMap` answer_opinions) $ \(Encryption{..}, _proof) ->- [encryption_nonce, encryption_vault]---- | @('signatureCommitments' voterZKP commitment)@-signatureCommitments :: SubGroup q => ZKP -> Commitment q -> BS.ByteString-signatureCommitments (ZKP voterZKP) commitment =- "sig|"<>voterZKP<>"|"<>bytesNat commitment<>"|"---- ** Type 'ErrorBallot'--- | Error raised by 'encryptBallot'.-data ErrorBallot- = ErrorBallot_WrongNumberOfAnswers Natural Natural- -- ^ When the number of answers- -- is different than the number of questions.- | ErrorBallot_Answer ErrorAnswer- -- ^ When 'encryptAnswer' raised an 'ErrorAnswer'.- deriving (Eq,Show)---- * Type 'DecryptionShare'--- | A decryption share. It is computed by a trustee from his/her--- private key share and the encrypted tally,--- and contains a cryptographic 'Proof' that he/she didn't cheat.-data DecryptionShare q = DecryptionShare- { decryptionShare_factors :: [[DecryptionFactor q]]- , decryptionShare_proofs :: [[Proof q]]- -- ^ 'Proof's that 'decryptionShare_factors' were correctly computed.- } deriving (Eq,Show)--computeDecryptionShare ::- Monad m => SubGroup q => RandomGen r =>- SecretKey q -> [[Encryption q]] -> S.StateT r m (DecryptionShare q)-computeDecryptionShare secKey encs = do- res <- mapM (mapM (decryptionFactor secKey)) encs- return $ uncurry DecryptionShare $ List.unzip (List.unzip <$> res)--decryptionFactor ::- Monad m => SubGroup q => RandomGen r =>- SecretKey q -> Encryption q -> S.StateT r m (DecryptionFactor q, Proof q)-decryptionFactor secKey Encryption{..} = do- proof <- prove secKey [groupGen, encryption_nonce] (hash zkp)- return (encryption_nonce^secKey, proof)- where zkp = decryptionStatement (publicKey secKey)--decryptionStatement :: SubGroup q => PublicKey q -> BS.ByteString-decryptionStatement pubKey =- "decrypt|"<>bytesNat pubKey<>"|"---- ** Type 'DecryptionFactor'-type DecryptionFactor = G---- ** Type 'ErrorDecryptionShare'-data ErrorDecryptionShare- = ErrorDecryptionShare_Invalid- deriving (Eq,Show)---- | @('checkDecryptionShare' encTally pubKey decShare)@--- checks that 'decShare'--- (supposedly submitted by a trustee whose public key is 'pubKey')--- is valid with respect to the encrypted tally 'encTally'.-checkDecryptionShare ::- Monad m => SubGroup q => RandomGen r =>- [[Encryption q]] -> PublicKey q -> DecryptionShare q ->- Exn.ExceptT ErrorDecryptionShare m Bool-checkDecryptionShare encTally pubKey DecryptionShare{..}- | len <- List.length encTally- , len == List.length decryptionShare_factors- , len == List.length decryptionShare_proofs =- Exn.throwE ErrorDecryptionShare_Invalid- | otherwise =- return $ and $ join $ List.zipWith3 (List.zipWith3- (\encFactor proof Encryption{..} ->- hash zkp- [ commit proof groupGen pubKey- , commit proof encryption_nonce encFactor- ] == proof_challenge proof- )) decryptionShare_factors decryptionShare_proofs encTally- where zkp = decryptionStatement pubKey--{--computeElectionResult ::- Natural ->- [Encryption q] ->- [DecryptionShare q] ->- ElectionResult q-computeElectionResult numBallots encTally decShares--}
+ README.md view
@@ -0,0 +1,57 @@+# Voting protocol++## Ballot+Ballots are encrypted using public-key cryptography+secured by the <i>Discrete Logarithm problem</i>:+finding `x` in <code>(g^x `mod` p)</code>, 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 <i>Decisional Diffe Hellman</i> (DDH) assumption,+all rely on the hardness of that problem.++### Ballot signing+The <i>Schnorr protocol</i> is used to prove that a voter has knowledge+of the secret key used to sign their votes.++### Voter's credential+A voter's credential is a secret key (the signing key)+from which a public part can be derived (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 <i>non-interactive zero-knowledge</i> (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.++## Tallying+Ballots are added without being decrypted+because adding (multiplying actually) ciphertexts then decrypting,+is like decrypting then adding plaintexts (<i>additive homomorphism</i>).+Which requires to solve the <i>Discrete Logarithm Problem</i>+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).++## Verifying+The <i>Chaum-Pedersen protocol</i> (proving an 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 <i>Disjunctive Chaum-Pedersen</i> proof of partial knowledge. +See: [Some ZK security proofs for Belenios](https://hal.inria.fr/hal-01576379).++A <i>strong Fiat-Shamir transformation</i> is used+to transform the <i>interactive zero-knowledge</i> (IZK) <i>Chaum-Pedersen protocol</i>+into a <i>non-interactive zero-knowledge</i> (NIZK) proof, using a SHA256 hash. +See: [How not to Prove Yourself: Pitfalls of the Fiat-Shamir Heuristic and Applications to Helios](https://eprint.iacr.org/2016/771.pdf).++## Public Key Infrastructure+(TODO) A Pedersen's <i>distributed key generation</i> (DKG) protocol+coupled with ElGamal keys (under the DDH assumption),+is used to have a fully distributed semantically secure encryption.
+ benchmarks/Election.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE OverloadedStrings #-}+module Election where++import qualified Data.List as List+import qualified Data.Text as Text+import qualified Text.Printf as Printf++import Voting.Protocol+import Utils++makeElection :: SubGroup q => Int -> Int -> Election q+makeElection nQuests nChoices = Election+ { election_name = Text.pack $ "elec"<>show nQuests<>show nChoices+ , election_description = "benchmarkable election"+ , election_uuid+ , election_publicKey =+ let secKey = credentialSecretKey election_uuid (Credential "xLcs7ev6Jy6FHHE") in+ publicKey secKey+ , election_hash = Hash "" -- FIXME: when implemented+ , election_questions =+ (<$> [1..nQuests]) $ \quest -> Question+ { question_text = Text.pack $ "quest"<>show quest+ , question_choices = (<$> [1..nChoices]) $ \choice -> Text.pack $ "choice"<>show choice+ , question_mini = one+ , question_maxi = one -- sum $ List.replicate nChoices one+ }+ } where election_uuid = UUID "xLcs7ev6Jy6FHH"++makeVotes :: Election q -> [[Bool]]+makeVotes Election{..} =+ [ True : List.tail [ False | _choice <- question_choices quest ]+ | quest <- election_questions+ ]++makeBallot :: forall q. SubGroup q => Election q -> Ballot q+makeBallot elec =+ case runExcept $ (`evalStateT` mkStdGen seed) $ do+ ballotSecKey :: SecretKey q <- randomSecretKey+ encryptBallot elec (Just ballotSecKey) $+ makeVotes elec of+ Right ballot -> ballot+ Left err -> error ("encryptBallot: "<>show err)+ where+ seed = 0++titleElection :: Election q -> String+titleElection Election{..} =+ Printf.printf "(questions=%i)×(choices=%i)==%i"+ nQuests nChoices (nQuests * nChoices)+ where+ nQuests = List.length election_questions+ nChoices = List.foldr max 0 $ List.length . question_choices <$> election_questions++benchEncryptBallot :: forall q. Params q => Int -> Int -> Benchmark+benchEncryptBallot nQuests nChoices =+ env setupEnv $ \ ~elec ->+ bench (titleElection elec) $+ nf makeBallot elec+ where+ setupEnv = do+ let elec :: Election q = makeElection nQuests nChoices+ return elec++benchVerifyBallot :: forall q. Params q => Int -> Int -> Benchmark+benchVerifyBallot nQuests nChoices =+ env setupEnv $ \ ~(elec,ballot) ->+ bench (titleElection elec) $+ nf (verifyBallot elec) ballot+ where+ setupEnv = do+ let elec :: Election q = makeElection nQuests nChoices+ let ballot = makeBallot elec+ return (elec,ballot)++benchmarks :: [Benchmark]+benchmarks =+ let inputs =+ [ (nQ,nC)+ | nQ <- [1,5,10,15,20,25]+ , nC <- [5,7]+ ] in+ [ bgroup "WeakParams"+ [ bgroup "encryptBallot"+ [ benchEncryptBallot @WeakParams nQuests nChoices+ | (nQuests,nChoices) <- inputs+ ]+ , bgroup "verifyBallot"+ [ benchVerifyBallot @BeleniosParams nQuests nChoices+ | (nQuests,nChoices) <- inputs+ ]+ ]+ , bgroup "BeleniosParams"+ [ bgroup "encryptBallot"+ [ benchEncryptBallot @BeleniosParams nQuests nChoices+ | (nQuests,nChoices) <- inputs+ ]+ , bgroup "verifyBallot"+ [ benchVerifyBallot @BeleniosParams nQuests nChoices+ | (nQuests,nChoices) <- inputs+ ]+ ]+ ]
+ benchmarks/Main.hs view
@@ -0,0 +1,11 @@+module Main where++import Criterion.Main+import qualified Election+import Utils++main :: IO ()+main =+ defaultMain $ join+ [ Election.benchmarks+ ]
+ benchmarks/Utils.hs view
@@ -0,0 +1,60 @@+module Utils+ ( module Criterion.Main+ , module Data.Bool+ , Applicative(..)+ , Monad(..), forM, join, replicateM, unless, when+ , Eq(..)+ , Either(..), either, isLeft, isRight+ , ($), (.), id, const, flip+ , (<$>)+ , Int+ , IO+ , Maybe(..)+ , Monoid(..), Semigroup(..)+ , Ord(..)+ , String+ , Text+ , Word8+ , Num, Fractional(..), Integral(..), Integer, fromIntegral+ , min+ , max+ , Show(..)+ , MonadTrans(..)+ , ExceptT+ , runExcept+ , throwE+ , StateT+ , evalStateT+ , mkStdGen+ , error+ , debug+ ) 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 Criterion.Main+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, error)+import System.IO (IO)+import System.Random (mkStdGen)+import Text.Show (Show(..))++debug :: Show a => String -> a -> a+debug msg x = trace (msg<>": "<>show x) x+
hjugement-protocol.cabal view
@@ -2,7 +2,7 @@ -- 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 @@ 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,10 +61,15 @@ type: git Library+ hs-source-dirs: src exposed-modules:- Protocol.Arithmetic- Protocol.Credential- Protocol.Election+ Voting.Protocol+ Voting.Protocol.Arithmetic+ Voting.Protocol.Credential+ Voting.Protocol.Election+ Voting.Protocol.Trustees+ Voting.Protocol.Trustees.All+ Voting.Protocol.Utils default-language: Haskell2010 default-extensions: AllowAmbiguousTypes@@ -141,8 +105,9 @@ -- , fixed-vector >= 1.1 -- , hashable >= 1.2.6 , memory >= 0.14- , mmorph >= 1.1+ -- , mmorph >= 1.1 -- , monad-classes >= 0.3+ , deepseq >= 1.4 , random >= 1.1 -- , reflection >= 2.1 , text >= 1.2@@ -151,15 +116,17 @@ 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 HUnit.Arithmetic HUnit.Credential HUnit.Election- HUnit.Utils- -- QuickCheck+ QuickCheck+ QuickCheck.Election+ QuickCheck.Trustee+ Utils default-language: Haskell2010 default-extensions: AllowAmbiguousTypes@@ -191,8 +158,8 @@ hjugement-protocol , base >= 4.6 && < 5 , containers >= 0.5- , hashable >= 1.2.6- , QuickCheck >= 2.0+ -- , hashable >= 1.2.6+ , QuickCheck >= 2.11 -- , monad-classes >= 0.3 , random >= 1.1 -- , reflection >= 2.1@@ -201,4 +168,47 @@ , tasty-quickcheck , text >= 1.2 , transformers >= 0.5- , unordered-containers >= 0.2.8+ -- , unordered-containers >= 0.2.8++Benchmark hjugement-protocol-benchmark+ type: exitcode-stdio-1.0+ hs-source-dirs: benchmarks+ main-is: Main.hs+ default-language: Haskell2010+ other-modules:+ Election+ Utils+ default-extensions:+ AllowAmbiguousTypes+ ConstraintKinds+ DefaultSignatures+ FlexibleContexts+ FlexibleInstances+ GeneralizedNewtypeDeriving+ LambdaCase+ MonoLocalBinds+ MultiParamTypeClasses+ NamedFieldPuns+ NoImplicitPrelude+ NoMonomorphismRestriction+ RecordWildCards+ ScopedTypeVariables+ TupleSections+ TypeApplications+ TypeFamilies+ TypeOperators+ UndecidableInstances+ ghc-options:+ -Wall+ -Wincomplete-uni-patterns+ -Wincomplete-record-updates+ -fno-warn-tabs+ build-depends:+ base >= 4.6 && < 5+ , hjugement-protocol+ , containers >= 0.5+ , criterion >= 1.4+ , QuickCheck >= 2.11+ , random >= 1.1+ , text >= 1.2+ , transformers >= 0.5
+ src/Voting/Protocol.hs view
@@ -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
+ src/Voting/Protocol/Arithmetic.hs view
@@ -0,0 +1,337 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+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+import Data.Eq (Eq(..))+import Data.Foldable (Foldable, foldl')+import Data.Function (($), (.))+import Data.Functor ((<$>))+import Data.Int (Int)+import Data.Maybe (Maybe(..))+import Data.Ord (Ord(..))+import Data.Semigroup (Semigroup(..))+import Data.String (String, IsString(..))+import Numeric.Natural (Natural)+import Prelude (Integer, Integral(..), fromIntegral, Enum(..))+import Text.Show (Show(..))+import qualified Control.Monad.Trans.State.Strict as S+import qualified Crypto.Hash as Crypto+import qualified Data.ByteArray as ByteArray+import qualified Data.ByteString as BS+import qualified Data.List as List+import qualified Prelude as Num+import qualified System.Random as Random++-- * Type 'F'+-- | The type of the elements of a 'PrimeField'.+--+-- A field must satisfy the following properties:+--+-- * @(f, ('+'), 'zero')@ forms an abelian group,+-- called the 'Additive' group of 'f'.+--+-- * @('NonNull' f, ('*'), 'one')@ forms an abelian group,+-- called the 'Multiplicative' group of 'f'.+--+-- * ('*') is associative:+-- @(a'*'b)'*'c == a'*'(b'*'c)@ and+-- @a'*'(b'*'c) == (a'*'b)'*'c@.+--+-- * ('*') and ('+') are both commutative:+-- @a'*'b == b'*'a@ and+-- @a'+'b == b'+'a@+--+-- * ('*') and ('+') are both left and right distributive:+-- @a'*'(b'+'c) == (a'*'b) '+' (a'*'c)@ and+-- @(a'+'b)'*'c == (a'*'c) '+' (b'*'c)@+--+-- The 'Natural' is always within @[0..'fieldCharac'-1]@.+newtype F p = F { unF :: Natural }+ deriving (Eq,Ord,Show,NFData)++instance PrimeField p => FromNatural (F p) where+ fromNatural i = F (abs (i `mod` fieldCharac @p))+ where abs x | x < 0 = x + fieldCharac @p+ | otherwise = x+instance ToNatural (F p) where+ nat = unF++instance PrimeField p => Additive (F p) where+ zero = F 0+ F x + F y = F ((x + y) `mod` fieldCharac @p)+instance PrimeField p => Negable (F p) where+ neg (F x) | x == 0 = zero+ | otherwise = F (fromIntegral (Num.negate (toInteger x) + toInteger (fieldCharac @p)))+instance PrimeField p => Multiplicative (F p) where+ one = F 1+ -- | Because 'fieldCharac' is prime,+ -- all elements of the field are invertible modulo 'fieldCharac'.+ F x * F y = F ((x * y) `mod` fieldCharac @p)+instance PrimeField p => Random.Random (F p) where+ randomR (F lo, F hi) =+ first (F . fromIntegral) .+ Random.randomR+ ( 0`max`toInteger lo+ , toInteger hi`min`(toInteger (fieldCharac @p) - 1))+ random = first (F . fromIntegral) . Random.randomR (0, toInteger (fieldCharac @p) - 1)++-- ** Class 'PrimeField'+-- | Parameter for a prime field.+class PrimeField p where+ -- | The prime number characteristic of a 'PrimeField'.+ --+ -- ElGamal's hardness to decrypt requires a large prime number+ -- to form the 'Multiplicative' 'SubGroup'.+ fieldCharac :: Natural++-- ** Class 'Additive'+class Additive a where+ zero :: a+ (+) :: a -> a -> a; infixl 6 ++ sum :: Foldable f => f a -> a+ sum = foldl' (+) zero+instance Additive Natural where+ zero = 0+ (+) = (Num.+)+instance Additive Integer where+ zero = 0+ (+) = (Num.+)+instance Additive Int where+ zero = 0+ (+) = (Num.+)++-- *** Class 'Negable'+class Additive a => Negable a where+ neg :: a -> a+ (-) :: a -> a -> a; infixl 6 -+ x-y = x + neg y+instance Negable Integer where+ neg = Num.negate+instance Negable Int where+ neg = Num.negate++-- ** Class 'Multiplicative'+class Multiplicative a where+ one :: a+ (*) :: a -> a -> a; infixl 7 *+instance Multiplicative Natural where+ one = 1+ (*) = (Num.*)+instance Multiplicative Integer where+ one = 1+ (*) = (Num.*)+instance Multiplicative Int where+ one = 1+ (*) = (Num.*)++-- ** Class 'Invertible'+class Multiplicative a => Invertible a where+ inv :: a -> a+ (/) :: a -> a -> a; infixl 7 /+ x/y = x * inv y++-- * 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,NFData)++instance PrimeField (P q) => FromNatural (G q) where+ fromNatural = G . fromNatural+instance ToNatural (G q) where+ nat = unF . unG++instance (SubGroup q, Multiplicative (F (P q))) => Multiplicative (G q) where+ one = G one+ G x * G y = G (x * y)+instance (SubGroup q, Multiplicative (F (P q))) => Invertible (G q) where+ -- | NOTE: add 'groupOrder' so the exponent given to (^) is positive.+ inv = (^ E (neg one + groupOrder @q))++-- ** Class 'SubGroup'+-- | A 'SubGroup' of a 'Multiplicative' group of a 'PrimeField'.+-- Used for signing (Schnorr) and encrypting (ElGamal).+class+ ( PrimeField (P q)+ , Multiplicative (F (P q))+ ) => SubGroup q where+ -- | Setting 'q' determines 'p', equals to @'P' q@.+ type P q :: *+ -- | A generator of the 'SubGroup'.+ -- NOTE: since @F p@ is a 'PrimeField',+ -- the 'Multiplicative' 'SubGroup' is cyclic,+ -- and there are phi('fieldCharac'-1) many choices for the generator of the group,+ -- where phi is the Euler totient function.+ groupGen :: G q+ -- | The order of the 'SubGroup'.+ --+ -- WARNING: 'groupOrder' MUST be a prime number dividing @('fieldCharac'-1)@+ -- to ensure that ElGamal is secure in terms of the DDH assumption.+ groupOrder :: F (P q)+ + -- | 'groupGenInverses' returns the infinite list+ -- of 'inv'erse powers of 'groupGen':+ -- @['groupGen' '^' 'neg' i | i <- [0..]]@,+ -- but by computing each value from the previous one.+ --+ -- NOTE: 'groupGenInverses' is in the 'SubGroup' class in order to keep+ -- computed terms in memory across calls to 'groupGenInverses'.+ --+ -- Used by 'intervalDisjunctions'.+ groupGenInverses :: [G q]+ groupGenInverses = go one+ where+ 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',+-- with a comma (",") intercalated between them.+--+-- NOTE: to avoid any collision when the 'hash' function is used in different contexts,+-- a message 'gs' is actually prefixed by a 'bs' indicating the context.+--+-- Used by 'proveEncryption' and 'verifyEncryption',+-- where the 'bs' usually contains the 'statement' to be proven,+-- and the 'gs' contains the 'commitments'.+hash ::+ SubGroup q =>+ BS.ByteString -> [G q] -> E q+hash bs gs =+ let s = bs <> BS.intercalate (fromString ",") (bytesNat <$> gs) in+ let h = ByteArray.convert (Crypto.hashWith Crypto.SHA256 s) in+ fromNatural (BS.foldl' (\acc b -> acc`shiftL`3 + fromIntegral b) (0::Natural) h)++-- * Type 'E'+-- | 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,NFData)++instance SubGroup q => FromNatural (E q) where+ fromNatural i = E (F (abs (i `mod` unF (groupOrder @q))))+ where abs x | x < 0 = x + unF (groupOrder @q)+ | otherwise = x+instance ToNatural (E q) where+ nat = unF . unE++instance (SubGroup q, Additive (F (P q))) => Additive (E q) where+ zero = E zero+ E (F x) + E (F y) = E (F ((x + y) `mod` unF (groupOrder @q)))+instance (SubGroup q, Negable (F (P q))) => Negable (E q) where+ neg (E (F x)) | x == 0 = zero+ | otherwise = E (F (fromIntegral ( neg (toInteger x)+ + toInteger (unF (groupOrder @q)) )))+instance (SubGroup q, Multiplicative (F (P q))) => Multiplicative (E q) where+ one = E one+ E (F x) * E (F y) = E (F ((x * y) `mod` unF (groupOrder @q)))+instance SubGroup q => Random.Random (E q) where+ randomR (E (F lo), E (F hi)) =+ first (E . F . fromIntegral) .+ Random.randomR+ ( 0`max`toInteger lo+ , toInteger hi`min`(toInteger (unF (groupOrder @q)) - 1) )+ random =+ first (E . F . fromIntegral) .+ Random.randomR (0, toInteger (unF (groupOrder @q)) - 1)+instance SubGroup q => Enum (E q) where+ toEnum = fromNatural . fromIntegral+ fromEnum = fromIntegral . nat+ enumFromTo lo hi = List.unfoldr+ (\i -> if i<=hi then Just (i, i+one) else Nothing) lo++infixr 8 ^+-- | @(b '^' e)@ returns the modular exponentiation of base 'b' by exponent 'e'.+(^) :: SubGroup q => G q -> E q -> G q+(^) b (E (F e))+ | e == zero = one+ | otherwise = t * (b*b) ^ E (F (e`shiftR`1))+ where+ t | testBit e 0 = b+ | otherwise = one++-- | @('randomR' i)@ returns a random integer in @[0..i-1]@.+randomR ::+ Monad m =>+ Random.RandomGen r =>+ Random.Random i =>+ Negable i =>+ Multiplicative i =>+ i -> S.StateT r m i+randomR i = S.StateT $ return . Random.randomR (zero, i-one)++-- | @('random')@ returns a random integer+-- in the range determined by its type.+random ::+ Monad m =>+ Random.RandomGen r =>+ Random.Random i =>+ Negable i =>+ Multiplicative i =>+ S.StateT r m i+random = S.StateT $ return . Random.random++instance Random.Random Natural where+ randomR (mini,maxi) =+ first (fromIntegral::Integer -> Natural) .+ Random.randomR (fromIntegral mini, fromIntegral maxi)+ random = first (fromIntegral::Integer -> Natural) . Random.random++-- * 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+instance PrimeField WeakParams where+ fieldCharac = 263+instance SubGroup WeakParams where+ type P WeakParams = WeakParams+ groupGen = G (F 2)+ groupOrder = F 131++-- ** Type 'BeleniosParams'+-- | Parameters used in Belenios.+-- A 2048-bit 'fieldCharac' of a 'PrimeField',+-- with a 256-bit 'groupOrder' for a 'Multiplicative' 'SubGroup'+-- generated by 'groupGen'.+data BeleniosParams+instance PrimeField BeleniosParams where+ fieldCharac = 20694785691422546401013643657505008064922989295751104097100884787057374219242717401922237254497684338129066633138078958404960054389636289796393038773905722803605973749427671376777618898589872735865049081167099310535867780980030790491654063777173764198678527273474476341835600035698305193144284561701911000786737307333564123971732897913240474578834468260652327974647951137672658693582180046317922073668860052627186363386088796882120769432366149491002923444346373222145884100586421050242120365433561201320481118852408731077014151666200162313177169372189248078507711827842317498073276598828825169183103125680162072880719+instance SubGroup BeleniosParams where+ type P BeleniosParams = BeleniosParams+ groupGen = G (F 2402352677501852209227687703532399932712287657378364916510075318787663274146353219320285676155269678799694668298749389095083896573425601900601068477164491735474137283104610458681314511781646755400527402889846139864532661215055797097162016168270312886432456663834863635782106154918419982534315189740658186868651151358576410138882215396016043228843603930989333662772848406593138406010231675095763777982665103606822406635076697764025346253773085133173495194248967754052573659049492477631475991575198775177711481490920456600205478127054728238140972518639858334115700568353695553423781475582491896050296680037745308460627)+ groupOrder = F 78571733251071885079927659812671450121821421258408794611510081919805623223441++-- * Conversions++-- ** Class 'FromNatural'+class FromNatural a where+ fromNatural :: Natural -> a++-- ** Class 'ToNatural'+class ToNatural a where+ nat :: a -> Natural++-- | @('bytesNat' x)@ returns the serialization of 'x'.+bytesNat :: ToNatural n => n -> BS.ByteString+bytesNat = fromString . show . nat
+ src/Voting/Protocol/Credential.hs view
@@ -0,0 +1,137 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+module Voting.Protocol.Credential where++import Control.DeepSeq (NFData)+import Control.Monad (Monad(..), replicateM)+import Data.Bits+import Data.Bool+import Data.Char (Char)+import Data.Either (Either(..))+import Data.Eq (Eq(..))+import Data.Function (($))+import Data.Functor ((<$>))+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)+import qualified Control.Monad.Trans.State.Strict as S+import qualified Crypto.KDF.PBKDF2 as Crypto+import qualified Data.ByteArray as ByteArray+import qualified Data.ByteString as BS+import qualified Data.Char as Char+import qualified Data.List as List+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified System.Random as Random++import Voting.Protocol.Arithmetic++-- * Type 'Credential'+-- | A 'Credential' is a word of @('tokenLength'+1 '==' 15)@-characters+-- from a base alphabet of (@'tokenBase' '==' 58)@ characters:+-- "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"+-- (beware the absence of "0", \"O", \"I", and "l").+-- 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)++credentialAlphabet :: [Char] -- TODO: make this an array+credentialAlphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"+tokenBase :: Int+tokenBase = List.length credentialAlphabet+tokenLength ::Int+tokenLength = 14++-- | @'randomCredential'@ generates a random 'Credential'.+randomCredential ::+ Monad m =>+ Random.RandomGen r =>+ S.StateT r m Credential+randomCredential = do+ rs <- replicateM tokenLength (randomR (fromIntegral tokenBase))+ let (tot, cs) = List.foldl' (\(acc,ds) d ->+ ( acc * tokenBase + d+ , charOfDigit d : ds )+ ) (zero::Int, []) rs+ let checksum = (neg tot + 53) `mod` 53 -- NOTE: why 53 and not 'tokenBase' ?+ return $ Credential $ Text.reverse $ Text.pack (charOfDigit checksum:cs)+ where+ charOfDigit = (credentialAlphabet List.!!)++-- | @'readCredential'@ reads and check the well-formedness of a 'Credential'+-- from raw 'Text'.+readCredential :: Text -> Either CredentialError Credential+readCredential s+ | Text.length s /= tokenLength + 1 = Left CredentialError_Length+ | otherwise = do+ tot <- Text.foldl'+ (\acc c -> acc >>= \a -> ((a * tokenBase) +) <$> digitOfChar c)+ (Right (zero::Int))+ (Text.init s)+ checksum <- digitOfChar (Text.last s)+ if (tot + checksum) `mod` 53 == 0+ then Right (Credential s)+ else Left CredentialError_Checksum+ where+ digitOfChar c =+ maybe (Left $ CredentialError_BadChar c) Right $+ List.elemIndex c credentialAlphabet++-- ** Type 'CredentialError'+data CredentialError+ = CredentialError_BadChar Char.Char+ | CredentialError_Checksum+ | CredentialError_Length+ deriving (Eq,Show,Generic,NFData)++-- ** Type 'UUID'+newtype UUID = UUID Text+ deriving (Eq,Ord,Show,Generic,NFData)++-- | @'randomUUID'@ generates a random 'UUID'.+randomUUID ::+ Monad m =>+ Random.RandomGen r =>+ S.StateT r m UUID+randomUUID = do+ rs <- replicateM tokenLength (randomR (fromIntegral tokenBase))+ return $ UUID $ Text.pack $ charOfDigit <$> rs+ where+ charOfDigit = (credentialAlphabet List.!!)++-- ** Type 'SecretKey'+type SecretKey = E++-- | @('credentialSecretKey' uuid cred)@ returns the 'SecretKey'+-- derived from given 'uuid' and 'cred'+-- using 'Crypto.fastPBKDF2_SHA256'.+credentialSecretKey :: SubGroup q => UUID -> Credential -> SecretKey q+credentialSecretKey (UUID uuid) (Credential cred) =+ fromNatural $ BS.foldl'+ (\acc b -> acc`shiftL`3 + fromIntegral b)+ (0::Natural)+ (ByteArray.convert deriv)+ where+ deriv :: BS.ByteString+ deriv =+ Crypto.fastPBKDF2_SHA256+ Crypto.Parameters+ { Crypto.iterCounts = 1000+ , Crypto.outputLength = 256 `div` 8+ }+ (Text.encodeUtf8 cred)+ (Text.encodeUtf8 uuid)++-- ** Type 'PublicKey'+type PublicKey = G++-- | @('publicKey' secKey)@ returns the 'PublicKey'+-- derived from given 'SecretKey'.+publicKey :: SubGroup q => SecretKey q -> PublicKey q+publicKey = (groupGen ^)
+ src/Voting/Protocol/Election.hs view
@@ -0,0 +1,670 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+module Voting.Protocol.Election where++import Control.DeepSeq (NFData)+import Control.Monad (Monad(..), join, mapM, replicateM, unless, zipWithM)+import Control.Monad.Trans.Class (MonadTrans(..))+import Control.Monad.Trans.Except (Except, ExceptT, runExcept, throwE, withExceptT)+import Data.Bool+import Data.Either (either)+import Data.Eq (Eq(..))+import Data.Foldable (Foldable, foldMap, and)+import Data.Function (($), id, const)+import Data.Functor (Functor, (<$>))+import Data.Functor.Identity (Identity(..))+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 Voting.Protocol.Utils+import Voting.Protocol.Arithmetic+import Voting.Protocol.Credential++-- * Type 'Encryption'+-- | ElGamal-like encryption.+-- Its security relies on the /Discrete Logarithm problem/.+--+-- Because ('groupGen' '^'encNonce '^'secKey '==' 'groupGen' '^'secKey '^'encNonce),+-- knowing @secKey@, one can divide 'encryption_vault' by @('encryption_nonce' '^'secKey)@+-- to decipher @('groupGen' '^'clear)@, then the @clear@ text must be small to be decryptable,+-- because it is encrypted as a power of 'groupGen' (hence the "-like" in "ElGamal-like")+-- to enable the additive homomorphism.+--+-- NOTE: Since @('encryption_vault' '*' 'encryption_nonce' '==' 'encryption_nonce' '^' (secKey '+' clear))@,+-- then: @(logBase 'encryption_nonce' ('encryption_vault' '*' 'encryption_nonce') '==' secKey '+' clear)@.+data Encryption q = Encryption+ { encryption_nonce :: G q+ -- ^ Public part of the randomness 'encNonce' used to 'encrypt' the 'clear' text,+ -- equal to @('groupGen' '^'encNonce)@+ , encryption_vault :: G q+ -- ^ Encrypted 'clear' text,+ -- equal to @('pubKey' '^'encNone '*' 'groupGen' '^'clear)@+ } deriving (Eq,Show,Generic,NFData)++-- | Additive homomorphism.+-- Using the fact that: @'groupGen' '^'x '*' 'groupGen' '^'y '==' 'groupGen' '^'(x'+'y)@.+instance SubGroup q => Additive (Encryption q) where+ zero = Encryption one one+ x+y = Encryption+ (encryption_nonce x * encryption_nonce y)+ (encryption_vault x * encryption_vault y)++-- *** Type 'EncryptionNonce'+type EncryptionNonce = E++-- | @('encrypt' pubKey clear)@ returns an ElGamal-like 'Encryption'.+--+-- WARNING: the secret encryption nonce (@encNonce@)+-- is returned alongside the 'Encryption'+-- in order to 'prove' the validity of the encrypted 'clear' text in 'proveEncryption',+-- but this secret @encNonce@ MUST be forgotten after that,+-- as it may be used to decipher the 'Encryption'+-- without the 'SecretKey' associated with 'pubKey'.+encrypt ::+ Monad m => RandomGen r => SubGroup q =>+ PublicKey q -> E q ->+ S.StateT r m (EncryptionNonce q, Encryption q)+encrypt pubKey clear = do+ encNonce <- random+ -- NOTE: preserve the 'encNonce' for 'prove' in 'proveEncryption'.+ return $ (encNonce,)+ Encryption+ { encryption_nonce = groupGen^encNonce+ , encryption_vault = pubKey ^encNonce * groupGen^clear+ }++-- * Type 'Proof'+-- | 'Proof' of knowledge of a discrete logarithm:+-- @(secret == logBase base (base^secret))@.+data Proof q = Proof+ { proof_challenge :: Challenge q+ -- ^ 'Challenge' sent by the verifier to the prover+ -- to ensure that the prover really has knowledge+ -- of the secret and is not replaying.+ -- Actually, 'proof_challenge' is not sent to the prover,+ -- but derived from the prover's 'Commitment's and statements+ -- with a collision resistant 'hash'.+ -- Hence the prover cannot chose the 'proof_challenge' to his/her liking.+ , proof_response :: E q+ -- ^ A discrete logarithm sent by the prover to the verifier,+ -- as a response to 'proof_challenge'.+ --+ -- If the verifier observes that @('proof_challenge' '==' 'hash' statement [commitment])@, where:+ --+ -- * @statement@ is a serialization of a tag, @base@ and @basePowSec@,+ -- * @commitment '==' 'commit' proof base basePowSec '=='+ -- base '^' 'proof_response' '*' basePowSec '^' 'proof_challenge'@,+ -- * and @basePowSec '==' base'^'sec@,+ --+ -- then, with overwhelming probability (due to the 'hash' function),+ -- the prover was not able to choose 'proof_challenge'+ -- yet was able to compute a 'proof_response' such that+ -- (@commitment '==' base '^' 'proof_response' '*' basePowSec '^' 'proof_challenge'@),+ -- 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,Generic,NFData)++-- ** Type 'ZKP'+-- | Zero-knowledge proof.+--+-- A protocol is /zero-knowledge/ if the verifier+-- learns nothing from the protocol except that the prover+-- knows the secret.+--+-- DOC: Mihir Bellare and Phillip Rogaway. Random oracles are practical:+-- A paradigm for designing efficient protocols. In ACM-CCS’93, 1993.+newtype ZKP = ZKP BS.ByteString++-- ** Type 'Challenge'+type Challenge = E++-- ** Type 'Oracle'+-- An 'Oracle' returns the 'Challenge' of the 'Commitment's+-- by 'hash'ing them (eventually with other 'Commitment's).+--+-- Used in 'prove' it enables a Fiat-Shamir transformation+-- of an /interactive zero-knowledge/ (IZK) proof+-- into a /non-interactive zero-knowledge/ (NIZK) proof.+-- That is to say that the verifier does not have+-- to send a 'Challenge' to the prover.+-- Indeed, the prover now handles the 'Challenge'+-- which becomes a (collision resistant) 'hash'+-- of the prover's commitments (and statements to be a stronger proof).+type Oracle list q = list (Commitment q) -> Challenge q++-- | @('prove' sec commitBases oracle)@+-- returns a 'Proof' that @sec@ is known+-- (by proving the knowledge of its discrete logarithm).+--+-- The 'Oracle' is given 'Commitment's equal to the 'commitBases'+-- raised to the power of the secret nonce of the 'Proof',+-- as those are the 'Commitment's that the verifier will obtain+-- when composing the 'proof_challenge' and 'proof_response' together+-- (with 'commit').+--+-- WARNING: for 'prove' to be a so-called /strong Fiat-Shamir transformation/ (not a weak):+-- 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@,+-- because two 'Proof's using the same 'Commitment'+-- can be used to deduce @sec@ (using the special-soundness).+prove ::+ Monad m => RandomGen r => SubGroup q => Functor list =>+ E q -> list (G q) -> Oracle list q -> S.StateT r m (Proof q)+prove sec commitBases oracle = do+ nonce <- random+ let commitments = (^ nonce) <$> commitBases+ let proof_challenge = oracle commitments+ return Proof+ { proof_challenge+ , proof_response = nonce - sec*proof_challenge+ }++-- | @('fakeProof')@ returns a 'Proof'+-- whose 'proof_challenge' and 'proof_response' are uniformly chosen at random,+-- instead of @('proof_challenge' '==' 'hash' statement commitments)@+-- and @('proof_response' '==' nonce '+' sec '*' 'proof_challenge')@+-- as a 'Proof' returned by 'prove'.+--+-- Used in 'proveEncryption' to fill the returned 'DisjProof'+-- with fake 'Proof's for all 'Disjunction's but the encrypted one.+fakeProof :: Monad m => RandomGen r => SubGroup q => S.StateT r m (Proof q)+fakeProof = do+ proof_challenge <- random+ proof_response <- random+ return Proof{..}++-- ** Type 'Commitment'+-- | A commitment from the prover to the verifier.+-- It's a power of 'groupGen' chosen randomly by the prover+-- when making a 'Proof' with 'prove'.+type Commitment = G++-- | @('commit' proof base basePowSec)@ returns a 'Commitment'+-- from the given 'Proof' with the knowledge of the verifier.+commit :: SubGroup q => Proof q -> G q -> G q -> Commitment q+commit Proof{..} base basePowSec =+ base^proof_response *+ basePowSec^proof_challenge+ -- NOTE: Contrary to some textbook presentations,+ -- @('*')@ is used instead of @('/')@ to avoid the performance cost+ -- of a modular exponentiation @('^' ('groupOrder' '-' 'one'))@,+ -- this is compensated by using @('-')@ instead of @('+')@ in 'prove'.+{-# INLINE commit #-}++-- * Type 'Disjunction'+-- | A 'Disjunction' is an 'inv'ersed @('groupGen' '^'opinion)@+-- it's used in 'proveEncryption' to generate a 'Proof'+-- that an 'encryption_vault' contains a given @('groupGen' '^'opinion)@,+type Disjunction = G++booleanDisjunctions :: SubGroup q => [Disjunction q]+booleanDisjunctions = List.take 2 groupGenInverses++intervalDisjunctions :: SubGroup q => Opinion q -> Opinion q -> [Disjunction q]+intervalDisjunctions mini maxi =+ List.genericTake (fromMaybe 0 $ (nat maxi + 1)`minusNaturalMaybe`nat mini) $+ List.genericDrop (nat mini) $+ groupGenInverses++-- ** Type 'Opinion'+-- | Index of a 'Disjunction' within a list of them.+-- It is encrypted as an 'E'xponent by 'encrypt'.+type Opinion = E++-- ** Type 'DisjProof'+-- | A list of 'Proof's to prove that the 'Opinion' within an 'Encryption'+-- is indexing a 'Disjunction' within a list of them,+-- without revealing which 'Opinion' it is.+newtype DisjProof q = DisjProof [Proof q]+ deriving (Eq,Show,Generic,NFData)++-- | @('proveEncryption' elecPubKey voterZKP (prevDisjs,nextDisjs) (encNonce,enc))@+-- returns a 'DisjProof' that 'enc' 'encrypt's+-- the 'Disjunction' 'd' between 'prevDisjs' and 'nextDisjs'.+--+-- The prover proves that it knows an 'encNonce', such that:+-- @(enc '==' Encryption{encryption_nonce='groupGen' '^'encNonce, encryption_vault=elecPubKey'^'encNonce '*' groupGen'^'d})@+--+-- A /NIZK Disjunctive Chaum Pedersen Logarithm Equality/ is used.+--+-- DOC: Pierrick Gaudry. <https://hal.inria.fr/hal-01576379 Some ZK security proofs for Belenios>, 2017.+proveEncryption ::+ Monad m => RandomGen r => SubGroup q =>+ PublicKey q -> ZKP ->+ ([Disjunction q],[Disjunction q]) ->+ (EncryptionNonce q, Encryption q) ->+ S.StateT r m (DisjProof q)+proveEncryption elecPubKey voterZKP (prevDisjs,nextDisjs) (encNonce,enc) = do+ -- Fake proofs for all 'Disjunction's except the genuine one.+ prevFakeProofs <- replicateM (List.length prevDisjs) fakeProof+ nextFakeProofs <- replicateM (List.length nextDisjs) fakeProof+ let fakeChallengeSum =+ sum (proof_challenge <$> prevFakeProofs) ++ sum (proof_challenge <$> nextFakeProofs)+ let statement = encryptionStatement voterZKP enc+ genuineProof <- prove encNonce [groupGen, elecPubKey] $ \genuineCommitments ->+ let validCommitments = List.zipWith (encryptionCommitments elecPubKey enc) in+ let prevCommitments = validCommitments prevDisjs prevFakeProofs in+ let nextCommitments = validCommitments nextDisjs nextFakeProofs in+ let commitments = join prevCommitments <> genuineCommitments <> join nextCommitments in+ let challenge = hash statement commitments in+ let genuineChallenge = challenge - fakeChallengeSum in+ genuineChallenge+ -- NOTE: here by construction (genuineChallenge == challenge - fakeChallengeSum)+ -- thus (sum (proof_challenge <$> proofs) == challenge)+ -- as checked in 'verifyEncryption'.+ let proofs = prevFakeProofs <> (genuineProof : nextFakeProofs)+ return (DisjProof proofs)++verifyEncryption ::+ Monad m => SubGroup q =>+ PublicKey q -> ZKP ->+ [Disjunction q] -> (Encryption q, DisjProof q) ->+ ExceptT ErrorVerifyEncryption m Bool+verifyEncryption elecPubKey voterZKP disjs (enc, DisjProof proofs) =+ case isoZipWith (encryptionCommitments elecPubKey enc) disjs proofs of+ Nothing ->+ throwE $ ErrorVerifyEncryption_InvalidProofLength+ (fromIntegral $ List.length proofs)+ (fromIntegral $ List.length disjs)+ Just commitments ->+ return $ challengeSum ==+ hash (encryptionStatement voterZKP enc) (join commitments)+ where+ challengeSum = sum (proof_challenge <$> proofs)++-- ** Hashing+encryptionStatement :: SubGroup q => ZKP -> Encryption q -> BS.ByteString+encryptionStatement (ZKP voterZKP) Encryption{..} =+ "prove|"<>voterZKP<>"|"+ <> bytesNat encryption_nonce<>","+ <> bytesNat encryption_vault<>"|"++-- | @('encryptionCommitments' elecPubKey enc disj proof)@+-- returns the 'Commitment's with only the knowledge of the verifier.+--+-- For the prover the 'Proof' comes from @fakeProof@,+-- and for the verifier the 'Proof' comes from the prover.+encryptionCommitments ::+ SubGroup q =>+ PublicKey q -> Encryption q ->+ Disjunction q -> Proof q -> [G q]+encryptionCommitments elecPubKey Encryption{..} disj proof =+ [ commit proof groupGen encryption_nonce+ -- == groupGen ^ nonce if 'Proof' comes from 'prove'.+ -- base==groupGen, basePowSec==groupGen^encNonce.+ , commit proof elecPubKey (encryption_vault*disj)+ -- == elecPubKey ^ nonce if 'Proof' comes from 'prove'+ -- and 'encryption_vault' encrypts (- logBase groupGen disj).+ -- base==elecPubKey, basePowSec==elecPubKey^encNonce.+ ]++-- ** Type 'ErrorVerifyEncryption'+-- | Error raised by 'verifyEncryption'.+data ErrorVerifyEncryption+ = ErrorVerifyEncryption_InvalidProofLength Natural Natural+ -- ^ When the number of proofs is different than+ -- the number of 'Disjunction's.+ deriving (Eq,Show)++-- * Type 'Question'+data Question q = Question+ { question_text :: Text+ , question_choices :: [Text]+ , question_mini :: Opinion q+ , question_maxi :: Opinion q+ -- , question_blank :: Maybe Bool+ } deriving (Eq,Show,Generic,NFData)++-- * Type 'Answer'+data Answer q = Answer+ { answer_opinions :: [(Encryption q, DisjProof q)]+ -- ^ Encrypted 'Opinion' for each 'question_choices'+ -- with a 'DisjProof' that they belong to [0,1].+ , answer_sumProof :: DisjProof q+ -- ^ Proofs that the sum of the 'Opinon's encrypted in 'answer_opinions'+ -- is an element of @[mini..maxi]@.+ -- , answer_blankProof ::+ } deriving (Eq,Show,Generic,NFData)++-- | @('encryptAnswer' elecPubKey zkp quest opinions)@+-- returns an 'Answer' validable by 'verifyAnswer',+-- unless an 'ErrorAnswer' is returned.+encryptAnswer ::+ Monad m => RandomGen r => SubGroup q =>+ PublicKey q -> ZKP ->+ Question q -> [Bool] ->+ S.StateT r (ExceptT ErrorAnswer m) (Answer q)+encryptAnswer elecPubKey zkp Question{..} opinionByChoice+ | not (question_mini <= opinionsSum && opinionsSum <= question_maxi) =+ lift $ throwE $+ ErrorAnswer_WrongSumOfOpinions+ (nat opinionsSum)+ (nat question_mini)+ (nat question_maxi)+ | List.length opinions /= List.length question_choices =+ lift $ throwE $+ ErrorAnswer_WrongNumberOfOpinions+ (fromIntegral $ List.length opinions)+ (fromIntegral $ List.length question_choices)+ | otherwise = do+ encryptions <- encrypt elecPubKey `mapM` opinions+ individualProofs <- zipWithM+ (\opinion -> proveEncryption elecPubKey zkp $+ if opinion+ then ([booleanDisjunctions List.!!0],[])+ else ([],[booleanDisjunctions List.!!1]))+ opinionByChoice encryptions+ sumProof <- proveEncryption elecPubKey zkp+ (List.tail <$> List.genericSplitAt+ (nat (opinionsSum - question_mini))+ (intervalDisjunctions question_mini question_maxi))+ ( sum (fst <$> encryptions) -- NOTE: sum the 'encNonce's+ , sum (snd <$> encryptions) -- NOTE: sum the 'Encryption's+ )+ return $ Answer+ { answer_opinions = List.zip+ (snd <$> encryptions) -- NOTE: drop encNonce+ individualProofs+ , answer_sumProof = sumProof+ }+ where+ opinionsSum = sum opinions+ opinions = (\o -> if o then one else zero) <$> opinionByChoice++verifyAnswer ::+ SubGroup q =>+ PublicKey q -> ZKP ->+ Question q -> Answer q -> Bool+verifyAnswer elecPubKey zkp Question{..} Answer{..}+ | List.length question_choices /= List.length answer_opinions = False+ | otherwise = either (const False) id $ runExcept $ do+ validOpinions <-+ verifyEncryption elecPubKey zkp booleanDisjunctions+ `traverse` answer_opinions+ validSum <- verifyEncryption elecPubKey zkp+ (intervalDisjunctions question_mini question_maxi)+ ( sum (fst <$> answer_opinions)+ , answer_sumProof )+ return (and validOpinions && validSum)++-- ** Type 'ErrorAnswer'+-- | Error raised by 'encryptAnswer'.+data ErrorAnswer+ = ErrorAnswer_WrongNumberOfOpinions Natural Natural+ -- ^ When the number of opinions is different than+ -- the number of choices ('question_choices').+ | ErrorAnswer_WrongSumOfOpinions Natural Natural Natural+ -- ^ When the sum of opinions is not within the bounds+ -- of 'question_mini' and 'question_maxi'.+ deriving (Eq,Show,Generic,NFData)++-- * Type 'Election'+data Election q = Election+ { election_name :: Text+ , election_description :: Text+ , election_publicKey :: PublicKey q+ , election_questions :: [Question q]+ , election_uuid :: UUID+ , election_hash :: Hash -- TODO: serialize to JSON to calculate this+ } deriving (Eq,Show,Generic,NFData)++-- ** Type 'Hash'+newtype Hash = Hash Text+ deriving (Eq,Ord,Show,Generic,NFData)++-- * Type 'Ballot'+data Ballot q = Ballot+ { ballot_answers :: [Answer q]+ , ballot_signature :: Maybe (Signature q)+ , ballot_election_uuid :: UUID+ , ballot_election_hash :: Hash+ } deriving (Generic,NFData)++-- | @('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'.+encryptBallot ::+ Monad m => RandomGen r => SubGroup q =>+ Election q -> Maybe (SecretKey q) -> [[Bool]] ->+ S.StateT r (ExceptT ErrorBallot m) (Ballot q)+encryptBallot Election{..} ballotSecKeyMay opinionsByQuest+ | List.length election_questions /= List.length opinionsByQuest =+ lift $ throwE $+ ErrorBallot_WrongNumberOfAnswers+ (fromIntegral $ List.length opinionsByQuest)+ (fromIntegral $ List.length election_questions)+ | otherwise = do+ let (voterKeys, voterZKP) =+ case ballotSecKeyMay of+ Nothing -> (Nothing, ZKP "")+ 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 (ballotSecKey, signature_publicKey) -> do+ signature_proof <-+ prove ballotSecKey (Identity groupGen) $+ \(Identity commitment) ->+ hash+ -- NOTE: the order is unusual, the commitments are first+ -- then comes the statement. Best guess is that+ -- this is easier to code due to their respective types.+ (signatureCommitments voterZKP commitment)+ (signatureStatement ballot_answers)+ return $ Just Signature{..}+ return Ballot+ { ballot_answers+ , ballot_election_hash = election_hash+ , ballot_election_uuid = election_uuid+ , ballot_signature+ }++verifyBallot :: SubGroup q => Election q -> Ballot q -> Bool+verifyBallot Election{..} Ballot{..} =+ ballot_election_uuid == election_uuid &&+ ballot_election_hash == election_hash &&+ List.length election_questions == List.length ballot_answers &&+ let (isValidSign, zkpSign) =+ case ballot_signature of+ Nothing -> (True, ZKP "")+ Just Signature{..} ->+ let zkp = ZKP (bytesNat signature_publicKey) in+ (, zkp) $+ proof_challenge signature_proof == hash+ (signatureCommitments zkp (commit signature_proof groupGen signature_publicKey))+ (signatureStatement ballot_answers)+ in+ and $ isValidSign :+ List.zipWith (verifyAnswer election_publicKey zkpSign)+ election_questions ballot_answers++-- ** Type 'Signature'+-- | Schnorr-like signature.+--+-- Used by each voter to sign his/her encrypted 'Ballot'+-- using his/her 'Credential',+-- in order to avoid ballot stuffing.+data Signature q = Signature+ { signature_publicKey :: PublicKey q+ -- ^ Verification key.+ , signature_proof :: Proof q+ } deriving (Generic,NFData)++-- *** Hashing++-- | @('signatureStatement' answers)@+-- returns the encrypted material to be signed:+-- all the 'encryption_nonce's and 'encryption_vault's of the given @answers@.+signatureStatement :: Foldable f => SubGroup q => f (Answer q) -> [G q]+signatureStatement =+ foldMap $ \Answer{..} ->+ (`foldMap` answer_opinions) $ \(Encryption{..}, _proof) ->+ [encryption_nonce, encryption_vault]++-- | @('signatureCommitments' voterZKP commitment)@+signatureCommitments :: SubGroup q => ZKP -> Commitment q -> BS.ByteString+signatureCommitments (ZKP voterZKP) commitment =+ "sig|"<>voterZKP<>"|" -- NOTE: this is actually part of the statement+ <> bytesNat commitment<>"|"++-- ** Type 'ErrorBallot'+-- | Error raised by 'encryptBallot'.+data ErrorBallot+ = ErrorBallot_WrongNumberOfAnswers Natural Natural+ -- ^ When the number of answers+ -- is different than the number of questions.+ | ErrorBallot_Answer ErrorAnswer+ -- ^ When 'encryptAnswer' raised an 'ErrorAnswer'.+ deriving (Eq,Show,Generic,NFData)++-- * Type 'DecryptionShare'+-- | A decryption share. It is computed by a trustee from his/her+-- private key share and the encrypted tally,+-- and contains a cryptographic 'Proof' that it didn't cheat.+data DecryptionShare q = DecryptionShare+ { decryptionShare_factors :: [[DecryptionFactor q]]+ -- ^ 'DecryptionFactor' by voter, by 'Question'.+ , decryptionShare_proofs :: [[Proof q]]+ -- ^ 'Proof's that 'decryptionShare_factors' were correctly computed.+ } deriving (Eq,Show,Generic,NFData)++-- BELENIOS: compute_factor+-- @('proveDecryptionShare' trusteeSecKey encByQuestByBallot)@+proveDecryptionShare ::+ Monad m => SubGroup q => RandomGen r =>+ SecretKey q -> [[Encryption q]] -> S.StateT r m (DecryptionShare q)+proveDecryptionShare secKey encs = do+ res <- (proveDecryptionFactor secKey `mapM`) `mapM` encs+ return $ uncurry DecryptionShare $ List.unzip (List.unzip <$> res)++-- BELENIOS: eg_factor+proveDecryptionFactor ::+ Monad m => SubGroup q => RandomGen r =>+ SecretKey q -> Encryption q -> S.StateT r m (DecryptionFactor q, Proof q)+proveDecryptionFactor secKey Encryption{..} = do+ proof <- prove secKey [groupGen, encryption_nonce] (hash zkp)+ return (encryption_nonce^secKey, proof)+ where zkp = decryptionShareStatement (publicKey secKey)++decryptionShareStatement :: SubGroup q => PublicKey q -> BS.ByteString+decryptionShareStatement pubKey =+ "decrypt|"<>bytesNat pubKey<>"|"++-- ** Type 'DecryptionFactor'+type DecryptionFactor = G++-- ** Type 'ErrorDecryptionShare'+data ErrorDecryptionShare+ = ErrorDecryptionShare_Invalid+ -- ^ The number of 'DecryptionFactor's or+ -- the number of 'Proof's is not the same+ -- or not the expected number.+ | ErrorDecryptionShare_Wrong+ -- ^ The 'Proof' of a 'DecryptionFactor' is wrong.+ deriving (Eq,Show,Generic,NFData)++-- BELENIOS: check_factor+-- | @('verifyDecryptionShare' encByQuestByBallot pubKey decShare)@+-- checks that 'decShare'+-- (supposedly submitted by a trustee whose public key is 'pubKey')+-- is valid with respect to the encrypted tally 'encByQuestByBallot'.+verifyDecryptionShare ::+ Monad m => SubGroup q =>+ [[Encryption q]] ->+ PublicKey q -> DecryptionShare q -> ExceptT ErrorDecryptionShare m ()+verifyDecryptionShare encByQuestByBallot pubKey DecryptionShare{..} =+ let zkp = decryptionShareStatement pubKey in+ isoZipWith3M_ (throwE ErrorDecryptionShare_Invalid)+ (isoZipWith3M_ (throwE ErrorDecryptionShare_Invalid) $+ \Encryption{..} decFactor proof ->+ unless (proof_challenge proof == hash zkp+ [ commit proof groupGen pubKey+ , commit proof encryption_nonce decFactor+ ]) $+ throwE ErrorDecryptionShare_Wrong)+ encByQuestByBallot+ decryptionShare_factors+ decryptionShare_proofs++-- * Type 'Tally'+data Tally q = Tally+ { tally_numBallots :: Natural+ , tally_encByQuestByBallot :: [[Encryption q]]+ -- ^ 'Encryption' by 'Question' by 'Ballot'.+ , tally_decShareByTrustee :: [DecryptionShare q]+ -- ^ 'DecryptionShare' by trustee.+ , tally_countByQuestByBallot :: [[Natural]]+ } deriving (Eq,Show,Generic,NFData)++type DecryptionShareCombinator q =+ [DecryptionShare q] -> Except ErrorDecryptionShare [[DecryptionFactor q]]++-- BELENIOS: compute_result+proveTally ::+ Monad m => SubGroup q =>+ [[Encryption q]] -> [DecryptionShare q] ->+ DecryptionShareCombinator q ->+ Except ErrorDecryptionShare (Tally q)+proveTally tally_encByQuestByBallot tally_decShareByTrustee decShareCombinator = do+ decFactorByQuestByBallot <- decShareCombinator tally_decShareByTrustee+ dec <- isoZipWithM err+ (\encByQuest decFactorByQuest ->+ maybe err return $+ isoZipWith (\Encryption{..} decFactor -> encryption_vault / decFactor)+ encByQuest+ decFactorByQuest+ )+ tally_encByQuestByBallot+ decFactorByQuestByBallot+ let tally_numBallots = fromIntegral $ List.length tally_encByQuestByBallot+ let logMap = Map.fromDistinctAscList $ List.zip groupGenPowers [0..tally_numBallots]+ let log x = maybe err return $ Map.lookup x logMap+ tally_countByQuestByBallot <- (log `mapM`)`mapM`dec+ return Tally{..}+ where err = throwE ErrorDecryptionShare_Invalid++verifyTally ::+ Monad m => SubGroup q =>+ DecryptionShareCombinator q -> Tally q ->+ Except ErrorDecryptionShare ()+verifyTally decShareCombinator Tally{..} = do+ decFactorByQuestByBallot <- decShareCombinator tally_decShareByTrustee+ isoZipWith3M_ (throwE ErrorDecryptionShare_Invalid)+ (isoZipWith3M_ (throwE ErrorDecryptionShare_Invalid)+ (\Encryption{..} decFactor count -> do+ let dec = encryption_vault / decFactor+ unless (dec == groupGen ^ fromNatural count) $+ throwE ErrorDecryptionShare_Wrong+ )+ )+ tally_encByQuestByBallot+ decFactorByQuestByBallot+ tally_countByQuestByBallot
+ src/Voting/Protocol/Trustees.hs view
@@ -0,0 +1,5 @@+module Voting.Protocol.Trustees+ ( module Voting.Protocol.Trustees.All+ ) where++import Voting.Protocol.Trustees.All
+ src/Voting/Protocol/Trustees/All.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE OverloadedStrings #-}+module Voting.Protocol.Trustees.All where++import Control.Monad (Monad(..), foldM, unless)+import Control.Monad.Trans.Except (ExceptT(..), throwE)+import Data.Eq (Eq(..))+import Data.Function (($))+import Data.Maybe (maybe)+import Data.Semigroup (Semigroup(..))+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 Voting.Protocol.Utils+import Voting.Protocol.Arithmetic+import Voting.Protocol.Credential+import Voting.Protocol.Election++-- * Type 'TrusteePublicKey'+data TrusteePublicKey q = TrusteePublicKey+ { trustee_PublicKey :: PublicKey q+ , trustee_SecretKeyProof :: Proof q+ } deriving (Eq,Show)++randomSecretKey :: Monad m => RandomGen r => SubGroup q => S.StateT r m (SecretKey q)+randomSecretKey = random++-- ** Type 'ErrorTrusteePublicKey'+data ErrorTrusteePublicKey+ = ErrorTrusteePublicKey_Wrong+ -- ^ The 'trustee_SecretKeyProof' is wrong.+ deriving (Eq,Show)++-- BELENIOS: MakeSimple.prove+-- | @('proveTrusteePublicKey' trustSecKey)@+-- returns the 'PublicKey' associated to 'trustSecKey'+-- and a 'Proof' of its knowledge.+proveTrusteePublicKey ::+ Monad m => RandomGen r => SubGroup q =>+ SecretKey q -> S.StateT r m (TrusteePublicKey q)+proveTrusteePublicKey trustSecKey = do+ let trustee_PublicKey = publicKey trustSecKey+ trustee_SecretKeyProof <-+ prove trustSecKey [groupGen] $+ hash (trusteePublicKeyStatement trustee_PublicKey)+ return TrusteePublicKey{..}++-- BELENIOS: MakeSimple.check+-- | @('verifyTrusteePublicKey' trustPubKey)@+-- returns 'True' iif. the given 'trustee_SecretKeyProof'+-- does prove that the 'SecretKey' associated with+-- the given 'trustee_PublicKey' is known by the trustee.+verifyTrusteePublicKey ::+ Monad m => SubGroup q =>+ TrusteePublicKey q ->+ ExceptT ErrorTrusteePublicKey m ()+verifyTrusteePublicKey TrusteePublicKey{..} =+ unless ((proof_challenge trustee_SecretKeyProof ==) $+ hash+ (trusteePublicKeyStatement trustee_PublicKey)+ [commit trustee_SecretKeyProof groupGen trustee_PublicKey]) $+ throwE ErrorTrusteePublicKey_Wrong++-- ** Hashing+trusteePublicKeyStatement :: PublicKey q -> BS.ByteString+trusteePublicKeyStatement trustPubKey = "pok|"<>bytesNat trustPubKey<>"|"++-- BELENIOS: MakeSimple.combine+electionPublicKey :: SubGroup q => [TrusteePublicKey q] -> PublicKey q+electionPublicKey = List.foldr (\TrusteePublicKey{..} -> (trustee_PublicKey *)) one++-- BELENIOS: combine_factors+-- | @('combineDecryptionShares' checker pubKeyByTrustee decShareByTrustee)@+-- returns the 'DecryptionFactor's by voter by question+combineDecryptionShares ::+ SubGroup q =>+ [[Encryption q]] ->+ [PublicKey q] -> DecryptionShareCombinator q+combineDecryptionShares encByQuestByBallot pubKeyByTrustee decShareByTrustee = do+ isoZipWithM_ (throwE ErrorDecryptionShare_Invalid)+ (verifyDecryptionShare encByQuestByBallot)+ pubKeyByTrustee+ decShareByTrustee+ (d0,ds) <- maybe err return $ List.uncons decShareByTrustee+ foldM+ (\decFactorByQuestByBallot DecryptionShare{..} ->+ isoZipWithM err+ (\acc df -> maybe err return $ isoZipWith (*) acc df)+ decFactorByQuestByBallot decryptionShare_factors)+ (decryptionShare_factors d0) ds+ where err = throwE ErrorDecryptionShare_Invalid
+ src/Voting/Protocol/Utils.hs view
@@ -0,0 +1,63 @@+module Voting.Protocol.Utils where++import Control.Applicative (Applicative(..))+import Data.Bool+import Data.Eq (Eq(..))+import Data.Foldable (sequenceA_)+import Data.Function (($))+import Data.Functor ((<$))+import Data.Maybe (Maybe(..), maybe)+import Data.Traversable (Traversable(..))+import qualified Data.List as List++-- | NOTE: check the lengths before applying @f@.+isoZipWith :: (a->b->c) -> [a]->[b]->Maybe [c]+isoZipWith f as bs+ | List.length as /= List.length bs = Nothing+ | otherwise = Just (List.zipWith f as bs)++-- | NOTE: check the lengths before applying @f@.+isoZipWith3 :: (a->b->c->d) -> [a]->[b]->[c]->Maybe [d]+isoZipWith3 f as bs cs+ | al /= List.length bs = Nothing+ | al /= List.length cs = Nothing+ | otherwise = Just (List.zipWith3 f as bs cs)+ where al = List.length as++isoZipWithM ::+ Applicative m =>+ m () ->+ (a -> b -> m c) ->+ [a] -> [b] -> m [c]+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 () ->+ (a -> b -> c -> m d) ->+ [a] -> [b] -> [c] -> m [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 ()+isoZipWith3M_ err f as bs cs =+ maybe err sequenceA_ $+ isoZipWith3 f as bs cs+
− test/HUnit.hs
@@ -1,13 +0,0 @@-module HUnit where-import Test.Tasty-import qualified HUnit.Arithmetic-import qualified HUnit.Credential-import qualified HUnit.Election--hunits :: TestTree-hunits =- testGroup "HUnit"- [ HUnit.Arithmetic.hunit- , HUnit.Credential.hunit- , HUnit.Election.hunit- ]
− test/HUnit/Arithmetic.hs
@@ -1,40 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE OverloadedStrings #-}-module HUnit.Arithmetic where--import Protocol.Arithmetic-import HUnit.Utils--hunit :: TestTree-hunit = testGroup "Arithmetic"- [ testGroup "inv"- [ testGroup "WeakParams"- [ testCase "groupGen" $- inv (groupGen @WeakParams) @?=- groupGen ^ E (groupOrder @WeakParams + neg one)- ]- , testGroup "BeleniosParams"- [ testCase "groupGen" $- inv (groupGen @BeleniosParams) @?=- groupGen ^ E (groupOrder @BeleniosParams + neg one)- ]- ]- , testGroup "hash"- [ testGroup "WeakParams"- [ testCase "[groupGen]" $- hash "start" [groupGen @WeakParams] @?=- fromNatural 100- , testCase "[groupGen, groupGen]" $- hash "start" [groupGen @WeakParams, groupGen] @?=- fromNatural 16- ]- , testGroup "BeleniosParams"- [ testCase "[groupGen]" $- hash "start" [groupGen @BeleniosParams] @?=- fromNatural 1832875488615060263192702367259- , testCase "[groupGen, groupGen]" $- hash "start" [groupGen @BeleniosParams, groupGen] @?=- fromNatural 2495277906542783643199702546512- ]- ]- ]
− test/HUnit/Credential.hs
@@ -1,53 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE OverloadedStrings #-}-module HUnit.Credential where--import Control.Applicative (Applicative(..))-import qualified Control.Monad.Trans.State.Strict as S-import qualified System.Random as Random--import Protocol.Arithmetic-import Protocol.Credential-import HUnit.Utils--hunit :: TestTree-hunit = testGroup "Credential"- [ testGroup "randomCredential"- [ testCase "0" $- S.evalState randomCredential (Random.mkStdGen 0) @?=- Credential "xLcs7ev6Jy6FHHE"- ]- , testGroup "randomUUID"- [ testCase "0" $- S.evalState randomUUID (Random.mkStdGen 0) @?=- UUID "xLcs7ev6Jy6FHH"- ]- , testGroup "readCredential" $- let (==>) inp exp =- testCase (show inp) $ readCredential inp @?= exp in- [ "" ==> Left CredentialError_Length- , "xLcs7ev6Jy6FH_E" ==> Left (CredentialError_BadChar '_')- , "xLcs7ev6Jy6FHIE" ==> Left (CredentialError_BadChar 'I')- , "xLcs7ev6Jy6FH0E" ==> Left (CredentialError_BadChar '0')- , "xLcs7ev6Jy6FHOE" ==> Left (CredentialError_BadChar 'O')- , "xLcs7ev6Jy6FHlE" ==> Left (CredentialError_BadChar 'l')- , "xLcs7ev6Jy6FH6" ==> Left CredentialError_Length- , "xLcs7ev6Jy6FHHy1" ==> Left CredentialError_Length- , "xLcs7ev6Jy6FHHF" ==> Left CredentialError_Checksum- , "xLcs7ev6Jy6FHHE" ==> Right (Credential "xLcs7ev6Jy6FHHE")- ]- , testGroup "secretKey" $- [ testSecretKey @WeakParams 0 $ E (F 122)- , testSecretKey @WeakParams 1 $ E (F 35)- , testSecretKey @BeleniosParams 0 $ E (F 2317630607062989137269685509390)- , testSecretKey @BeleniosParams 1 $ E (F 1968146140481358915910346867611)- ]- ]--testSecretKey :: forall q. SubGroup q => Int -> E q -> TestTree-testSecretKey seed exp =- let (uuid@(UUID u), cred@(Credential c)) =- (`S.evalState` Random.mkStdGen seed) $- (,) <$> randomUUID <*> randomCredential in- testCase (show (u,c)) $- secretKey @q uuid cred @?= exp
− test/HUnit/Election.hs
@@ -1,111 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}-{-# 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 qualified Data.List as List-import qualified System.Random as Random--import Protocol.Arithmetic-import Protocol.Credential-import Protocol.Election-import HUnit.Utils---- * Type 'Params'-class SubGroup q => Params q where- paramsName :: String-instance Params WeakParams where- paramsName = "WeakParams"-instance Params BeleniosParams where- paramsName = "BeleniosParams"--hunit :: TestTree-hunit = testGroup "Election"- [ testGroup "groupGenInverses"- [ testCase "WeakParams" $- List.take 10 (groupGenInverses @WeakParams) @?=- [groupGen^neg (fromNatural n) | n <- [0..9]]- , testCase "BeleniosParams" $- List.take 10 (groupGenInverses @BeleniosParams) @?=- [groupGen^neg (fromNatural n) | n <- [0..9]]- ]- , testGroup "encryptBallot" $- [ testsEncryptBallot @WeakParams- , testsEncryptBallot @BeleniosParams- ]- ]--testsEncryptBallot :: forall q. Params q => TestTree-testsEncryptBallot =- testGroup (paramsName @q)- [ testEncryptBallot @q 0- [Question "q1" ["a1","a2","a3"] zero one]- [[True, False, False]]- (Right True)- , testEncryptBallot @q 0- [Question "q1" ["a1","a2","a3"] zero one]- [[False, False, False]]- (Right True)- , testEncryptBallot @q 0- [Question "q1" ["a1","a2","a3"] zero one]- [[False, False, False]]- (Right True)- , testEncryptBallot @q 0- [Question "q1" [] zero one]- []- (Left (ErrorBallot_WrongNumberOfAnswers 0 1))- , testEncryptBallot @q 0- [Question "q1" ["a1","a2"] one one]- [[True]]- (Left (ErrorBallot_Answer (ErrorAnswer_WrongNumberOfOpinions 1 2)))- , testEncryptBallot @q 0- [Question "q1" ["a1","a2","a3"] zero one]- [[True, True, False]]- (Left (ErrorBallot_Answer (ErrorAnswer_WrongSumOfOpinions 2 0 1)))- , testEncryptBallot @q 0- [Question "q1" ["a1","a2","a3"] one one]- [[False, False, False]]- (Left (ErrorBallot_Answer (ErrorAnswer_WrongSumOfOpinions 0 1 1)))- , testEncryptBallot @q 0- [Question "q1" ["a1","a2"] one one]- [[False, False, True]]- (Left (ErrorBallot_Answer (ErrorAnswer_WrongNumberOfOpinions 3 2)))- , testEncryptBallot @q 0- [ Question "q1" ["a11","a12","a13"] zero (one+one)- , Question "q2" ["a21","a22","a23"] one one- ]- [ [True, False, True]- , [False, True, False] ]- (Right True)- ]--testEncryptBallot ::- forall q. SubGroup q =>- Int -> [Question q] -> [[Bool]] ->- Either ErrorBallot Bool ->- TestTree-testEncryptBallot seed quests opins exp =- let verify =- Exn.runExcept $- (`S.evalStateT` Random.mkStdGen seed) $ do- uuid <- randomUUID- cred <- randomCredential- let secKey = secretKey @q uuid cred- let pubKey = publicKey secKey- let elec = Election- { election_name = "election"- , election_description = "description"- , election_publicKey = pubKey- , election_questions = quests- , election_uuid = uuid- , election_hash = Hash ""- }- verifyBallot elec- <$> encryptBallot elec (Just secKey) opins- in- testCase (show opins) $- verify @?= exp
− test/HUnit/Utils.hs
@@ -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(..))
− test/Main.hs
@@ -1,15 +0,0 @@-module Main where--import System.IO (IO)-import Data.Function (($))-import Test.Tasty--- import QuickCheck-import HUnit--main :: IO ()-main =- defaultMain $- testGroup "Protocol"- [ hunits- -- , quickchecks- ]
+ tests/HUnit.hs view
@@ -0,0 +1,13 @@+module HUnit where+import Test.Tasty+import qualified HUnit.Arithmetic+import qualified HUnit.Credential+import qualified HUnit.Election++hunits :: TestTree+hunits =+ testGroup "HUnit"+ [ HUnit.Arithmetic.hunit+ , HUnit.Credential.hunit+ , HUnit.Election.hunit+ ]
+ tests/HUnit/Arithmetic.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE OverloadedStrings #-}+module HUnit.Arithmetic where++import Test.Tasty.HUnit+import Protocol.Arithmetic+import Utils++hunit :: TestTree+hunit = testGroup "Arithmetic"+ [ testGroup "inv"+ [ testGroup "WeakParams"+ [ testCase "groupGen" $+ inv (groupGen @WeakParams) @?=+ groupGen ^ E (groupOrder @WeakParams + neg one)+ ]+ , testGroup "BeleniosParams"+ [ testCase "groupGen" $+ inv (groupGen @BeleniosParams) @?=+ groupGen ^ E (groupOrder @BeleniosParams + neg one)+ ]+ ]+ , testGroup "hash"+ [ testGroup "WeakParams"+ [ testCase "[groupGen]" $+ hash "start" [groupGen @WeakParams] @?=+ fromNatural 100+ , testCase "[groupGen, groupGen]" $+ hash "start" [groupGen @WeakParams, groupGen] @?=+ fromNatural 16+ ]+ , testGroup "BeleniosParams"+ [ testCase "[groupGen]" $+ hash "start" [groupGen @BeleniosParams] @?=+ fromNatural 1832875488615060263192702367259+ , testCase "[groupGen, groupGen]" $+ hash "start" [groupGen @BeleniosParams, groupGen] @?=+ fromNatural 2495277906542783643199702546512+ ]+ ]+ ]
+ tests/HUnit/Credential.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE OverloadedStrings #-}+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 Utils++hunit :: TestTree+hunit = testGroup "Credential"+ [ testGroup "randomCredential"+ [ testCase "0" $+ S.evalState randomCredential (Random.mkStdGen 0) @?=+ Credential "xLcs7ev6Jy6FHHE"+ ]+ , testGroup "randomUUID"+ [ testCase "0" $+ S.evalState randomUUID (Random.mkStdGen 0) @?=+ UUID "xLcs7ev6Jy6FHH"+ ]+ , testGroup "readCredential" $+ let (==>) inp exp =+ testCase (show inp) $ readCredential inp @?= exp in+ [ "" ==> Left CredentialError_Length+ , "xLcs7ev6Jy6FH_E" ==> Left (CredentialError_BadChar '_')+ , "xLcs7ev6Jy6FHIE" ==> Left (CredentialError_BadChar 'I')+ , "xLcs7ev6Jy6FH0E" ==> Left (CredentialError_BadChar '0')+ , "xLcs7ev6Jy6FHOE" ==> Left (CredentialError_BadChar 'O')+ , "xLcs7ev6Jy6FHlE" ==> Left (CredentialError_BadChar 'l')+ , "xLcs7ev6Jy6FH6" ==> Left CredentialError_Length+ , "xLcs7ev6Jy6FHHy1" ==> Left CredentialError_Length+ , "xLcs7ev6Jy6FHHF" ==> Left CredentialError_Checksum+ , "xLcs7ev6Jy6FHHE" ==> Right (Credential "xLcs7ev6Jy6FHHE")+ ]+ , testGroup "credentialSecretKey" $+ [ testSecretKey @WeakParams 0 $ E (F 122)+ , testSecretKey @WeakParams 1 $ E (F 35)+ , testSecretKey @BeleniosParams 0 $ E (F 2317630607062989137269685509390)+ , testSecretKey @BeleniosParams 1 $ E (F 1968146140481358915910346867611)+ ]+ ]++testSecretKey :: forall q. SubGroup q => Int -> E q -> TestTree+testSecretKey seed exp =+ let (uuid@(UUID u), cred@(Credential c)) =+ (`S.evalState` Random.mkStdGen seed) $+ (,) <$> randomUUID <*> randomCredential in+ testCase (show (u,c)) $+ credentialSecretKey @q uuid cred @?= exp
+ tests/HUnit/Election.hs view
@@ -0,0 +1,126 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-}+module HUnit.Election where++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 Protocol.Trustees.Simple++import Utils++hunit :: TestTree+hunit = testGroup "Election"+ [ testGroup "groupGenInverses"+ [ testCase "WeakParams" $+ List.take 10 (groupGenInverses @WeakParams) @?=+ [groupGen^neg (fromNatural n) | n <- [0..9]]+ , testCase "BeleniosParams" $+ List.take 10 (groupGenInverses @BeleniosParams) @?=+ [groupGen^neg (fromNatural n) | n <- [0..9]]+ ]+ , testGroup "encryptBallot" $+ [ testsEncryptBallot @WeakParams+ , testsEncryptBallot @BeleniosParams+ ]+ , testGroup "trustee" $+ [ testsTrustee @WeakParams+ ]+ ]++testsEncryptBallot :: forall q. Params q => TestTree+testsEncryptBallot =+ testGroup (paramsName @q)+ [ testEncryptBallot @q 0+ [Question "q1" ["a1","a2","a3"] zero one]+ [[True, False, False]]+ (Right True)+ , testEncryptBallot @q 0+ [Question "q1" ["a1","a2","a3"] zero one]+ [[False, False, False]]+ (Right True)+ , testEncryptBallot @q 0+ [Question "q1" ["a1","a2","a3"] zero one]+ [[False, False, False]]+ (Right True)+ , testEncryptBallot @q 0+ [Question "q1" [] zero one]+ []+ (Left (ErrorBallot_WrongNumberOfAnswers 0 1))+ , testEncryptBallot @q 0+ [Question "q1" ["a1","a2"] one one]+ [[True]]+ (Left (ErrorBallot_Answer (ErrorAnswer_WrongNumberOfOpinions 1 2)))+ , testEncryptBallot @q 0+ [Question "q1" ["a1","a2","a3"] zero one]+ [[True, True, False]]+ (Left (ErrorBallot_Answer (ErrorAnswer_WrongSumOfOpinions 2 0 1)))+ , testEncryptBallot @q 0+ [Question "q1" ["a1","a2","a3"] one one]+ [[False, False, False]]+ (Left (ErrorBallot_Answer (ErrorAnswer_WrongSumOfOpinions 0 1 1)))+ , testEncryptBallot @q 0+ [Question "q1" ["a1","a2"] one one]+ [[False, False, True]]+ (Left (ErrorBallot_Answer (ErrorAnswer_WrongNumberOfOpinions 3 2)))+ , testEncryptBallot @q 0+ [ Question "q1" ["a11","a12","a13"] zero (one+one)+ , Question "q2" ["a21","a22","a23"] one one+ ]+ [ [True, False, True]+ , [False, True, False] ]+ (Right True)+ ]++testEncryptBallot ::+ forall q. SubGroup q =>+ Int -> [Question q] -> [[Bool]] ->+ Either ErrorBallot Bool ->+ TestTree+testEncryptBallot seed quests opins exp =+ let verify =+ runExcept $+ (`evalStateT` Random.mkStdGen seed) $ do+ uuid <- randomUUID+ cred <- randomCredential+ let ballotSecKey = credentialSecretKey @q uuid cred+ let elecPubKey = publicKey ballotSecKey -- FIXME: wrong key+ let elec = Election+ { election_name = "election"+ , election_description = "description"+ , election_publicKey = elecPubKey+ , election_questions = quests+ , election_uuid = uuid+ , election_hash = Hash "" -- FIXME: when implemented+ }+ verifyBallot elec+ <$> 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
+ tests/Main.hs view
@@ -0,0 +1,15 @@+module Main where++import System.IO (IO)+import Data.Function (($))+import Test.Tasty+import QuickCheck+import HUnit++main :: IO ()+main =+ defaultMain $+ testGroup "Protocol"+ [ hunits+ , quickchecks+ ]
+ tests/QuickCheck.hs view
@@ -0,0 +1,11 @@+module QuickCheck where+import Test.Tasty+import qualified QuickCheck.Election+import qualified QuickCheck.Trustee++quickchecks :: TestTree+quickchecks =+ testGroup "QuickCheck"+ [ QuickCheck.Election.quickcheck+ , QuickCheck.Trustee.quickcheck+ ]
+ tests/QuickCheck/Election.hs view
@@ -0,0 +1,151 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS -fno-warn-orphans #-}+module QuickCheck.Election where++import Test.Tasty.QuickCheck+import qualified Data.List as List+import qualified Data.Text as Text+import Data.Eq (Eq(..))+import Data.Int (Int)+import Data.Ord (Ord(..))+import Prelude (undefined)++import Protocol.Arithmetic+import Protocol.Credential+import Protocol.Election++import Utils++-- Hardcoded limits to avoid keep a reasonable testing time.+maxArbitraryChoices :: Natural+maxArbitraryChoices = 5+maxArbitraryQuestions :: Natural+maxArbitraryQuestions = 5++quickcheck :: TestTree+quickcheck =+ testGroup "Election"+ [ testGroup "verifyBallot" $+ [ testElection @WeakParams+ , testElection @BeleniosParams+ ]+ ]++testElection :: forall q. Params q => TestTree+testElection =+ testGroup (paramsName @q)+ [ testProperty "Right" $ \(seed, (elec::Election q) :> votes) ->+ isRight $ runExcept $+ (`evalStateT` mkStdGen seed) $ do+ -- ballotSecKey :: SecretKey q <- randomSecretKey+ ballot <- encryptBallot elec Nothing votes+ unless (verifyBallot elec ballot) $+ lift $ throwE $ ErrorBallot_WrongNumberOfAnswers 0 0+ ]++instance PrimeField p => Arbitrary (F p) where+ arbitrary = choose (zero, F (fieldCharac @p) - one)+instance SubGroup q => Arbitrary (G q) where+ arbitrary = do+ m <- arbitrary+ return (groupGen ^ m)+instance SubGroup q => Arbitrary (E q) where+ arbitrary = E <$> choose (zero, groupOrder @q - one)+instance Arbitrary UUID where+ arbitrary = do+ seed <- arbitrary+ (`evalStateT` mkStdGen seed) $ do+ randomUUID+instance SubGroup q => Arbitrary (Proof q) where+ arbitrary = do+ proof_challenge <- arbitrary+ proof_response <- arbitrary+ return Proof{..}+instance SubGroup q => Arbitrary (Question q) where+ arbitrary = do+ let question_text = "question"+ choices :: Natural <- choose (1, maxArbitraryChoices)+ let question_choices = [Text.pack ("c"<>show c) | c <- [1..choices]]+ question_mini <- fromNatural <$> choose (0, choices)+ question_maxi <- fromNatural <$> choose (nat question_mini, choices)+ return Question{..}+ shrink quest =+ [ quest{question_choices, question_mini, question_maxi}+ | question_choices <- shrinkList pure $ question_choices quest+ , let nChoices = fromNatural $ fromIntegral $ List.length question_choices+ , question_mini <- shrink $ min nChoices $ max zero $ question_mini quest+ , question_maxi <- shrink $ min nChoices $ max question_mini $ question_maxi quest+ ]+instance SubGroup q => Arbitrary (Election q) where+ arbitrary = do+ let election_name = "election"+ let election_description = "description"+ election_publicKey <- arbitrary+ election_questions <- resize (fromIntegral maxArbitraryQuestions) $ listOf1 arbitrary+ election_uuid <- arbitrary+ let election_hash = Hash ""+ return Election{..}+ shrink elec =+ [ elec{election_questions}+ | election_questions <- shrink $ election_questions elec+ ]++-- | A type to declare an 'Arbitrary' instance where @b@ depends on @a@.+data (:>) a b = a :> b+ deriving (Eq,Show)+instance SubGroup q => Arbitrary (Question q :> [Bool]) where+ arbitrary = do+ quest@Question{..} <- arbitrary+ votes <- do+ let numChoices = List.length question_choices+ numTrue <- fromIntegral <$> choose (nat question_mini, nat question_maxi)+ rank <- choose (0, nCk numChoices numTrue - 1)+ return $ boolsOfCombin numChoices numTrue rank+ return (quest :> votes)+ shrink (quest :> votes) =+ [ q :> shrinkVotes q votes+ | q <- shrink quest+ ]+instance SubGroup q => Arbitrary (Election q :> [[Bool]]) where+ arbitrary = do+ elec@Election{..} <- arbitrary+ votes <- forM election_questions $ \Question{..} -> do+ let numChoices = List.length question_choices+ numTrue <- fromIntegral <$> choose (nat question_mini, nat question_maxi)+ rank <- choose (0, nCk numChoices numTrue - 1)+ return $ boolsOfCombin numChoices numTrue rank+ return (elec :> votes)+ shrink (elec :> votes) =+ [ e :> List.zipWith shrinkVotes (election_questions e) votes+ | e <- shrink elec+ ]++-- | @('boolsOfCombin' nBits nTrue rank)@ returns the 'rank'-th sequence of 'nBits'-bits possible+-- with 'nTrue' bits set at 'True' and @(nBits-nTrue)@ set at 'False'.+-- @rank@ has to be in @[0 .. 'nCk' nBits nTrue '-' 1]@+boolsOfCombin :: Int -> Int -> Int -> [Bool]+boolsOfCombin nBits nTrue rank+ | rank < 0 = undefined+ | nTrue == 0 = List.replicate nBits False+ | otherwise = go 0 cs <> List.replicate (nBits-List.last cs) False+ where+ cs = combinOfRank nBits nTrue rank+ go _d [] = []+ go curr (next:ns) =+ List.replicate (next-1-curr) False <> [True]+ <> go next ns++-- | @('shrinkVotes' quest votes)@+-- returns a reduced version of the given @votes@+-- to fit the requirement of the given @quest@.+shrinkVotes :: Question q -> [Bool] -> [Bool]+shrinkVotes Question{..} votes =+ (\(nTrue, b) -> if nTrue <= nat question_maxi then b else False)+ <$> List.zip (countTrue votes) votes+ where+ countTrue :: [Bool] -> [Natural]+ countTrue = List.foldl' (\ns b -> if b then inc ns else ns) []+ where+ inc [] = [zero]+ inc (n:ns) = (n+one):n:ns+
+ tests/QuickCheck/Trustee.hs view
@@ -0,0 +1,37 @@+{-# OPTIONS -fno-warn-orphans #-}+module QuickCheck.Trustee where++import Test.Tasty.QuickCheck++import Protocol.Arithmetic+import Protocol.Credential+import Protocol.Trustees.Simple++import Utils+import QuickCheck.Election ()++quickcheck :: TestTree+quickcheck =+ testGroup "Trustee"+ [ testGroup "verifyTrusteePublicKey" $+ [ testTrusteePublicKey @WeakParams+ , testTrusteePublicKey @BeleniosParams+ ]+ ]++testTrusteePublicKey :: forall q. Params q => TestTree+testTrusteePublicKey =+ testGroup (paramsName @q)+ [ testProperty "Right" $ \seed ->+ isRight $ runExcept $+ (`evalStateT` mkStdGen seed) $ do+ trustSecKey :: SecretKey q <- randomSecretKey+ trustPubKey <- proveTrusteePublicKey trustSecKey+ lift $ verifyTrusteePublicKey trustPubKey+ ]++instance SubGroup q => Arbitrary (TrusteePublicKey q) where+ arbitrary = do+ trustee_PublicKey <- arbitrary+ trustee_SecretKeyProof <- arbitrary+ return TrusteePublicKey{..}
+ tests/Utils.hs view
@@ -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