protocol: use Belenios' JSON schema
authorJulien Moutinho <julm+hjugement@autogeree.net>
Fri, 12 Jul 2019 18:20:58 +0000 (18:20 +0000)
committerJulien Moutinho <julm+hjugement@autogeree.net>
Sun, 14 Jul 2019 01:23:58 +0000 (01:23 +0000)
hjugement-protocol/hjugement-protocol.cabal
hjugement-protocol/src/Voting/Protocol/Election.hs
hjugement-protocol/src/Voting/Protocol/FFC.hs
hjugement-protocol/src/Voting/Protocol/Tally.hs
hjugement-protocol/src/Voting/Protocol/Trustee/Indispensable.hs

index 280debd3c0e52de0a8774290e6f15671ba20258d..51b5e94b4c914c894201f6f1e3eb3fd533fd282e 100644 (file)
@@ -2,7 +2,7 @@ name: hjugement-protocol
 -- PVP:  +-+------- breaking API changes
 --       | | +----- non-breaking API additions
 --       | | | +--- code changes with no API change
-version: 0.0.3.20190701
+version: 0.0.4.20190711
 category: Politic
 synopsis: A cryptographic protocol for the Majority Judgment.
 description:
index 12f59b58c1ad84ea7e31be9240109d4c8b3bd25b..2b03f26b1a88a8989e654ef969d43dad50b49f0f 100644 (file)
@@ -11,7 +11,7 @@ import Control.DeepSeq (NFData)
 import Control.Monad (Monad(..), join, mapM, replicateM, zipWithM)
 import Control.Monad.Trans.Class (MonadTrans(..))
 import Control.Monad.Trans.Except (ExceptT, runExcept, throwE, withExceptT)
-import Data.Aeson (ToJSON(..),FromJSON(..),(.:),(.=))
+import Data.Aeson (ToJSON(..),FromJSON(..),(.:),(.:?),(.=))
 import Data.Bool
 import Data.Either (either)
 import Data.Eq (Eq(..))
@@ -63,7 +63,11 @@ data Encryption c = Encryption
    -- equal to @('pubKey' '^'encNone '*' 'groupGen' '^'clear)@
  } deriving (Eq,Show,Generic,NFData)
 deriving instance Reifies c FFC => ToJSON (Encryption c)
-deriving instance Reifies c FFC => FromJSON (Encryption c)
+instance Reifies c FFC => FromJSON (Encryption c) where
+       parseJSON = JSON.withObject "Encryption" $ \o -> do
+               encryption_nonce <- o .: "alpha"
+               encryption_vault <- o .: "beta"
+               return Encryption{..}
 
 -- | Additive homomorphism.
 -- Using the fact that: @'groupGen' '^'x '*' 'groupGen' '^'y '==' 'groupGen' '^'(x'+'y)@.
@@ -133,8 +137,22 @@ data Proof c = Proof
    -- to ensure that each 'prove' does not reveal any information
    -- about its secret.
  } deriving (Eq,Show,Generic,NFData)
-deriving instance Reifies c FFC => ToJSON (Proof c)
-deriving instance Reifies c FFC => FromJSON (Proof c)
+instance ToJSON (Proof c) where
+       toJSON Proof{..} =
+               JSON.object
+                [ "challenge" .= proof_challenge
+                , "response"  .= proof_response
+                ]
+       toEncoding Proof{..} =
+               JSON.pairs
+                (  "challenge" .= proof_challenge
+                <> "response"  .= proof_response
+                )
+instance Reifies c FFC => FromJSON (Proof c) where
+       parseJSON = JSON.withObject "TrusteePublicKey" $ \o -> do
+               proof_challenge <- o .: "challenge"
+               proof_response  <- o .: "response"
+               return Proof{..}
 
 -- ** Type 'ZKP'
 -- | Zero-knowledge proof.
@@ -369,7 +387,13 @@ data Answer c = Answer
  -- , answer_blankProof ::
  } deriving (Eq,Show,Generic,NFData)
 deriving instance Reifies c FFC => ToJSON (Answer c)
-deriving instance Reifies c FFC => FromJSON (Answer c)
+instance Reifies c FFC => FromJSON (Answer c) where
+       parseJSON = JSON.withObject "Answer" $ \o -> do
+               answer_choices <- o .: "choices"
+               answer_individual_proofs <- o .: "individual_proofs"
+               let answer_opinions = List.zip answer_choices answer_individual_proofs
+               answer_sumProof <- o .: "overall_proof"
+               return Answer{..}
 
 -- | @('encryptAnswer' elecPubKey zkp quest opinions)@
 -- returns an 'Answer' validable by 'verifyAnswer',
@@ -491,7 +515,6 @@ reifyElection Election{..} k =
                reify ffc $ \(_::Proxy c) -> k @c
                 Election{election_crypto = ElectionCrypto_FFC ffc (G (F pubKey)), ..}
 
-
 instance ToJSON (ElectionCrypto c) where
        toJSON (ElectionCrypto_FFC ffc pubKey) =
                JSON.object
@@ -534,7 +557,13 @@ data Ballot c = Ballot
  , ballot_election_hash :: !Hash
  } deriving (Generic,NFData)
 deriving instance Reifies c FFC => ToJSON (Ballot c)
-deriving instance Reifies c FFC => FromJSON (Ballot c)
+instance Reifies c FFC => FromJSON (Ballot c) where
+       parseJSON = JSON.withObject "Ballot" $ \o -> do
+               ballot_answers       <- o .: "answers"
+               ballot_signature     <- o .:? "signature"
+               ballot_election_uuid <- o .: "election_uuid"
+               ballot_election_hash <- o .: "election_hash"
+               return Ballot{..}
 
 -- | @('encryptBallot' elec ('Just' ballotSecKey) opinionsByQuest)@
 -- returns a 'Ballot' signed by 'secKey' (the voter's secret key)
@@ -616,7 +645,13 @@ data Signature c = Signature
  , signature_proof     :: !(Proof c)
  } deriving (Generic,NFData)
 deriving instance Reifies c FFC => ToJSON (Signature c)
-deriving instance Reifies c FFC => FromJSON (Signature c)
+instance Reifies c FFC => FromJSON (Signature c) where
+       parseJSON = JSON.withObject "Signature" $ \o -> do
+               signature_publicKey <- o .: "public_key"
+               proof_challenge     <- o .: "challenge"
+               proof_response      <- o .: "response"
+               let signature_proof = Proof{..}
+               return Signature{..}
 
 -- *** Hashing
 
index d37f4e51b0ea92e053687a9a205a2a7187b4ff71..23278286e9ba3ff371d162bb659af2ff108a0f95 100644 (file)
@@ -197,7 +197,7 @@ instance Reifies c FFC => FromJSON (F c) where
         , Just x <- readMaybe (Text.unpack s)
         , x < fieldCharac @c
         = return (F x)
-       parseJSON json = JSON.typeMismatch "F" json
+       parseJSON json = JSON.typeMismatch "FieldElement" json
 instance Reifies c FFC => FromNatural (F c) where
        fromNatural i = F $ abs $ i `mod` fieldCharac @c
                where
@@ -288,7 +288,7 @@ instance Reifies c FFC => FromJSON (G c) where
         , r <- G (F x)
         , r ^ E (groupOrder @c) == one
         = return r
-       parseJSON json = JSON.typeMismatch "G" json
+       parseJSON json = JSON.typeMismatch "GroupElement" json
 instance Reifies c FFC => FromNatural (G c) where
        fromNatural = G . fromNatural
 instance ToNatural (G c) where
@@ -374,7 +374,7 @@ instance Reifies c FFC => FromJSON (E c) where
         , Just x <- readMaybe (Text.unpack s)
         , x < groupOrder @c
         = return (E x)
-       parseJSON json = JSON.typeMismatch "E" json
+       parseJSON json = JSON.typeMismatch "Exponent" json
 
 instance Reifies c FFC => FromNatural (E c) where
        fromNatural i =
index 46bfd864d41046845ffbe26c6306c8973fdc9cd4..eef45154af72d9890e400b35f7b7669a28c58757 100644 (file)
@@ -1,5 +1,6 @@
 {-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE UndecidableInstances #-} -- for Reifies instances
 module Voting.Protocol.Tally where
@@ -7,17 +8,19 @@ module Voting.Protocol.Tally where
 import Control.DeepSeq (NFData)
 import Control.Monad (Monad(..), mapM, unless)
 import Control.Monad.Trans.Except (Except, ExceptT, throwE)
-import Data.Aeson (ToJSON(..),FromJSON(..))
+import Data.Aeson (ToJSON(..),FromJSON(..),(.:),(.=))
 import Data.Eq (Eq(..))
-import Data.Function (($))
+import Data.Function (($), (.))
 import Data.Functor ((<$>))
 import Data.Maybe (maybe)
 import Data.Semigroup (Semigroup(..))
-import Data.Tuple (fst)
+import Data.Tuple (fst, snd)
 import GHC.Generics (Generic)
 import Numeric.Natural (Natural)
-import Prelude (fromIntegral)
 import Text.Show (Show(..))
+import qualified Data.Aeson as JSON
+import qualified Data.Aeson.Types as JSON
+import qualified Data.Aeson.Encoding as JSON
 import qualified Control.Monad.Trans.State.Strict as S
 import qualified Data.ByteString as BS
 import qualified Data.List as List
@@ -55,15 +58,22 @@ type EncryptedTally c = [[Encryption c]]
 -- returns the sum of the 'Encryption's of the given @ballots@,
 -- along with the number of 'Ballot's.
 encryptedTally :: Reifies c FFC => [Ballot c] -> (EncryptedTally c, Natural)
-encryptedTally ballots =
-       ( List.foldr (\Ballot{..} ->
-               List.zipWith (\Answer{..} ->
-                       List.zipWith (+)
-                        (fst <$> answer_opinions))
-                ballot_answers)
-        (List.repeat (List.repeat zero))
-        ballots
-       , fromIntegral $ List.length ballots
+encryptedTally = List.foldr insertEncryptedTally emptyEncryptedTally
+
+-- | The initial 'EncryptedTally' which tallies no 'Ballot'.
+emptyEncryptedTally :: Reifies c FFC => (EncryptedTally c, Natural)
+emptyEncryptedTally = (List.repeat (List.repeat zero), 0)
+
+-- | @('insertEncryptedTally' ballot encTally)@
+-- returns the 'EncryptedTally' adding the votes of the given @(ballot)@
+-- to those of the given @(encTally)@.
+insertEncryptedTally :: Reifies c FFC => Ballot c -> (EncryptedTally c, Natural) -> (EncryptedTally c, Natural)
+insertEncryptedTally Ballot{..} (encTally, numBallots) =
+       ( List.zipWith
+                (\Answer{..} -> List.zipWith (+) (fst <$> answer_opinions))
+                ballot_answers
+                encTally
+       , numBallots+1
        )
 
 -- ** Type 'DecryptionShareCombinator'
@@ -114,7 +124,34 @@ verifyTally Tally{..} decShareCombinator = do
 -- ** Type 'DecryptionShare'
 -- | A decryption share is a 'DecryptionFactor' and a decryption 'Proof', by choice by 'Question'.
 -- Computed by a trustee in 'proveDecryptionShare'.
-type DecryptionShare c = [[(DecryptionFactor c, Proof c)]]
+newtype DecryptionShare c = DecryptionShare
+ { unDecryptionShare :: [[(DecryptionFactor c, Proof c)]] }
+ deriving (Eq,Show,Generic)
+deriving newtype instance NFData (DecryptionShare c)
+instance ToJSON (DecryptionShare c) where
+       toJSON (DecryptionShare decByChoiceByQuest) =
+               JSON.object
+                [ "decryption_factors" .=
+                       toJSONList (((toJSON . fst) <$>) <$> decByChoiceByQuest)
+                , "decryption_proofs" .=
+                       toJSONList (((toJSON . snd) <$>) <$> decByChoiceByQuest)
+                ]
+       toEncoding (DecryptionShare decByChoiceByQuest) =
+               JSON.pairs $
+                       JSON.pair "decryption_factors"
+                        (JSON.list (JSON.list (toEncoding . fst)) decByChoiceByQuest) <>
+                       JSON.pair "decryption_proofs"
+                        (JSON.list (JSON.list (toEncoding . snd)) decByChoiceByQuest)
+instance Reifies c FFC => FromJSON (DecryptionShare c) where
+       parseJSON = JSON.withObject "DecryptionShare" $ \o -> do
+               decFactors <- o .: "decryption_factors"
+               decProofs  <- o .: "decryption_proofs"
+               let err msg = JSON.typeMismatch ("DecryptionShare: "<>msg) (JSON.Object o)
+               DecryptionShare
+                <$> isoZipWithM (err "inconsistent number of questions")
+                        (isoZipWithM (err "inconsistent number of choices")
+                                (\a b -> return (a, b)))
+                decFactors decProofs
 
 -- *** Type 'DecryptionFactor'
 -- | @'encryption_nonce' '^'trusteeSecKey@
@@ -125,6 +162,7 @@ proveDecryptionShare ::
  Monad m => Reifies c FFC => RandomGen r =>
  EncryptedTally c -> SecretKey c -> S.StateT r m (DecryptionShare c)
 proveDecryptionShare encByChoiceByQuest trusteeSecKey =
+       (DecryptionShare <$>) $
        (proveDecryptionFactor trusteeSecKey `mapM`) `mapM` encByChoiceByQuest
 
 proveDecryptionFactor ::
@@ -163,7 +201,7 @@ verifyDecryptionShare ::
  Monad m => Reifies c FFC =>
  EncryptedTally c -> PublicKey c -> DecryptionShare c ->
  ExceptT ErrorTally m ()
-verifyDecryptionShare encByChoiceByQuest trusteePubKey =
+verifyDecryptionShare encByChoiceByQuest trusteePubKey (DecryptionShare decShare) =
        let zkp = decryptionShareStatement trusteePubKey in
        isoZipWithM_ (throwE ErrorTally_NumberOfQuestions)
         (isoZipWithM_ (throwE ErrorTally_NumberOfChoices) $
@@ -173,6 +211,7 @@ verifyDecryptionShare encByChoiceByQuest trusteePubKey =
                 , commit proof encryption_nonce decFactor
                 ]) $ throwE ErrorTally_WrongProof)
         encByChoiceByQuest
+        decShare
 
 verifyDecryptionShareByTrustee ::
  Monad m => Reifies c FFC =>
index fbd035b185cc8e47506b638afde0f77c22d57ccc..b211679d8bf7ff2fa313312903e591b208490514 100644 (file)
@@ -7,7 +7,7 @@ module Voting.Protocol.Trustee.Indispensable where
 import Control.DeepSeq (NFData)
 import Control.Monad (Monad(..), foldM, unless)
 import Control.Monad.Trans.Except (ExceptT(..), throwE)
-import Data.Aeson (ToJSON(..),FromJSON(..))
+import Data.Aeson (ToJSON(..),FromJSON(..),(.:),(.=))
 import Data.Eq (Eq(..))
 import Data.Function (($))
 import Data.Functor ((<$>))
@@ -17,6 +17,7 @@ import Data.Tuple (fst)
 import GHC.Generics (Generic)
 import Text.Show (Show(..))
 import qualified Control.Monad.Trans.State.Strict as S
+import qualified Data.Aeson as JSON
 import qualified Data.ByteString as BS
 import qualified Data.List as List
 
@@ -44,8 +45,22 @@ data TrusteePublicKey c = TrusteePublicKey
        -- Which is done in 'proveIndispensableTrusteePublicKey'
        -- and 'verifyIndispensableTrusteePublicKey'.
  } deriving (Eq,Show,Generic,NFData)
-deriving instance Reifies c FFC => ToJSON (TrusteePublicKey c)
-deriving instance Reifies c FFC => FromJSON (TrusteePublicKey c)
+instance ToJSON (TrusteePublicKey c) where
+       toJSON TrusteePublicKey{..} =
+               JSON.object
+                [ "pok"        .= trustee_SecretKeyProof
+                , "public_key" .= trustee_PublicKey
+                ]
+       toEncoding TrusteePublicKey{..} =
+               JSON.pairs
+                (  "pok"        .= trustee_SecretKeyProof
+                <> "public_key" .= trustee_PublicKey
+                )
+instance Reifies c FFC => FromJSON (TrusteePublicKey c) where
+       parseJSON = JSON.withObject "TrusteePublicKey" $ \o -> do
+               trustee_PublicKey <- o .: "public_key"
+               trustee_SecretKeyProof <- o .: "pok"
+               return TrusteePublicKey{..}
 
 -- ** Type 'ErrorTrusteePublicKey'
 data ErrorTrusteePublicKey
@@ -113,10 +128,10 @@ combineIndispensableDecryptionShares
         encByChoiceByQuest
         pubKeyByTrustee
         decByChoiceByQuestByTrustee
-       (dec0,decs) <-
+       (DecryptionShare dec0,decs) <-
                maybe (throwE ErrorTally_NumberOfTrustees) return $
                List.uncons decByChoiceByQuestByTrustee
        foldM (isoZipWithM (throwE ErrorTally_NumberOfQuestions)
         (maybe (throwE ErrorTally_NumberOfChoices) return `o2`
                isoZipWith (\a (decFactor, _proof) -> a * decFactor)))
-        ((fst <$>) <$> dec0) decs
+        ((fst <$>) <$> dec0) (unDecryptionShare <$> decs)