protocol: Fix Election
authorJulien Moutinho <julm+hjugement@autogeree.net>
Mon, 22 Apr 2019 22:57:01 +0000 (22:57 +0000)
committerJulien Moutinho <julm+hjugement@autogeree.net>
Mon, 22 Apr 2019 23:07:10 +0000 (23:07 +0000)
hjugement-protocol/Protocol/Election.hs
hjugement-protocol/test/HUnit/Election.hs

index 1cfb26cf4aad7c1422c9be28c7c1267bbd9c79d8..01d2f39be21caaf750af85d86b8c41db3644b53b 100644 (file)
+{-# LANGUAGE OverloadedStrings #-}
 module Protocol.Election where
 
-import Control.Monad (Monad(..))
+import Control.Monad (Monad(..), mapM, forM, join, sequence)
 import Data.Bool
 import Data.Eq (Eq(..))
-import Data.Functor (Functor, (<$>))
+import Data.Function (($), (.))
+import Data.Functor (Functor(..), (<$>))
+import Data.Foldable (Foldable, foldMap, and, toList)
+import Data.Int (Int)
 import Data.Maybe (Maybe(..))
 import Data.Monoid (Monoid(..))
-import Data.Ord (Ord(..))
-import Data.Int (Int)
+import Data.Ord (Ord(..), Ordering(..))
 import Data.Semigroup (Semigroup(..))
-import Prelude (Integral, undefined)
+import Data.Text (Text)
+import Data.Tuple (fst, snd, curry)
+import Prelude (Integral, fromIntegral, undefined, error)
 import Text.Show (Show(..))
+import Data.String (IsString(..))
 import qualified Control.Monad.Trans.State.Strict as S
 import qualified Data.List as List
+import qualified Data.Text.Encoding as Text
+import qualified Data.ByteString as BS
+import qualified Data.Map.Strict as Map
+import Data.Map.Strict (Map)
 
 import Protocol.Arith
+import Protocol.List
 
--- * Type 'CipherText'
-data CipherText q = CipherText
- { alpha :: G q -- ^ Random nonce: @(g'^'r)@
- , beta  :: G q -- ^ Encrypted message: @(g'^'msg '*' (g'^'secKey)'^'r)@
+-- * Type 'Encryption'
+data Encryption q = Encryption
+ { encryption_nonce :: G q
+   -- ^ Public part of the random 'secNonce': @('groupGen''^'r)@
+ , encryption_vault :: G q
+   -- ^ Encrypted opinion: @('pubKey''^'r '*' 'groupGen''^'opinion)@
  } deriving (Show)
 
 -- | Additive homomorphism.
--- Using the fact that: @g^x * g^y == g^(x+y)@.
-instance SubGroup q => Semigroup (CipherText q) where
-       x<>y = CipherText (alpha x * alpha y) (beta x * beta y)
-instance SubGroup q => Monoid (CipherText q) where
-       mempty = CipherText one one
-       mappend = (<>)
+-- 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 PublicKey = G
 type SecretKey = E
-type Random = E
+type SecretNonce = E
+type PublicKeyString = BS.ByteString
+
+-- *** Type 'Opinion'
+-- | Exponent indexing a 'Disjunction' within a list of them.
+type Opinion = E
 
 encrypt ::
- PrimeField (P q) =>
+ Monad m =>
+ RandomGen r =>
  SubGroup q =>
- Integral msg =>
- PublicKey q -> Random q -> msg -> CipherText q
-encrypt pk r msg =
-       CipherText
-        { alpha = groupGen^r
-        , beta  = groupGen^inE msg * pk^r
-        -- NOTE: pk == groupGen ^ sk
-        -- NOTE: msg is put as exponent in order
-        -- to make an additive homomorphism
-        -- instead of a multiplicative homomorphism.
-        }
+ PublicKey q -> Opinion q ->
+ S.StateT r m (SecretNonce q, Encryption q)
+encrypt pubKey opinion = do
+       secNonce <- random
+       -- NOTE: preserve the 'secNonce' for 'proof'
+       return $ (secNonce,)
+               Encryption
+                { encryption_nonce = groupGen^secNonce
+                , encryption_vault = pubKey  ^secNonce * groupGen^opinion
+                -- NOTE: pubKey == groupGen ^ secKey
+                -- NOTE: 'index' is put as exponent in order
+                -- to make an additive homomorphism
+                -- instead of a multiplicative homomorphism.
+                }
 
 -- * Type 'Proof'
 data Proof q = Proof
- { challenge :: E q
- , response  :: E q
+ { proof_challenge :: E q
+ , proof_response  :: E q
  } deriving (Eq,Show)
 
-proofFiatShamir ::
+type Oracle q = [Commitment q] -> Hash q
+type Hash = E
+
+-- | Strong Fiat-Shamir transformation
+-- of an IZK proof into a NIZK proof.
+proof ::
  Monad m =>
  RandomGen r =>
  SubGroup q =>
- Functor f =>
- f (G q) -> E q -> (f (G q) -> E q) -> S.StateT r m (Proof q)
-proofFiatShamir gs msg oracle = do
-       r <- random
-       let commitments = (^ r) <$> gs
-       let challenge = oracle commitments
+ SecretNonce q ->
+ [Commitment q] ->
+ Oracle q ->
+ S.StateT r m (Proof q)
+proof secretNonce commits oracle = do
+       nonce <- random
+       let commitments = (^ nonce) <$> commits
+       let proof_challenge = oracle commitments
        return Proof
-        { challenge
-        , response = r + msg * challenge
+        { proof_challenge
+        , proof_response = nonce + secretNonce*proof_challenge
+        }
+
+-- ** Type 'Commitment'
+type Commitment = G
+
+-- ** Type 'Disjunction'
+-- | A 'Disjunction' is an 'inv'ersed @'groupGen''^'opinion@
+-- it's used in 'validableEncryption' to generate a 'Proof'
+-- that an 'encryption_vault' contains a given @'groupGen''^'opinion@,
+type Disjunction = G
+
+validBool :: SubGroup q => [Disjunction q]
+validBool = List.take 2 groupGenInverses
+
+validRange :: SubGroup q => E q -> E q -> [Disjunction q]
+validRange mini maxi =
+       List.genericTake (intE maxi - intE mini) $
+       List.genericDrop (intE mini) groupGenInverses
+
+-- ** Type 'ValidityProof'
+-- | A list of 'Proof' to prove that the 'Opinion' within an 'Encryption'
+-- is indexing a 'Disjunction' within a list of them,
+-- without knowing which 'Opinion' it is.
+newtype ValidityProof q = ValidityProof [Proof q]
+ deriving (Eq,Show)
+
+-- | @('validableEncryption' pubKey zkp ds d (secNonce, enc))@
+-- returns a 'ValidityProof' that @'encryption_nonce' == 'groupGen''^''secNonce'@
+-- and @'encryption_vault' == pubKey'^'secNonce'/'indexedDisj'@.
+validableEncryption ::
+ forall m r q.
+ Monad m =>
+ RandomGen r =>
+ SubGroup q =>
+ PublicKey q -> PublicKeyString ->
+ [Disjunction q] -> Opinion q ->
+ (SecretNonce q, Encryption q) ->
+ S.StateT r m (ValidityProof q)
+validableEncryption pubKey zkp valids index (secNonce, Encryption{..})
+ | (prevDisjs,_indexedDisj:nextDisjs) <-
+   List.splitAt (fromIntegral (intE index)) valids = do
+       prevFakes <- fakeProof `mapM` prevDisjs
+       nextFakes <- fakeProof `mapM` nextDisjs
+       let challengeSum =
+               neg $
+               sum (proof_challenge . fst <$> prevFakes) +
+               sum (proof_challenge . fst <$> nextFakes)
+       genuineProof <- proof secNonce [groupGen, pubKey] $
+        -- | 'Oracle'
+        \nizkCommitments ->
+               let statement =
+                       "prove|"<>zkp<>"|"<>
+                       fromString (show (intG encryption_nonce))<>","<>
+                       fromString (show (intG encryption_vault))<>"|" in
+               let commitments =
+                       foldMap snd prevFakes <>
+                       nizkCommitments <>
+                       foldMap snd nextFakes in
+               hash statement commitments + challengeSum
+       return $
+               ValidityProof $
+                (fst <$> prevFakes) <>
+                [genuineProof] <>
+                (fst <$> nextFakes)
+ | otherwise = error "validableEncryption: bad disjunction index"
+       where
+       fakeProof :: Disjunction q -> S.StateT r m (Proof q, [Commitment q])
+       fakeProof disj = do
+               proof_challenge <- random
+               proof_response  <- random
+               let commitments =
+                        [ groupGen^proof_response / encryption_nonce       ^proof_challenge
+                        , pubKey  ^proof_response / (encryption_vault*disj)^proof_challenge
+                        ]
+               return (Proof{..}, commitments)
+
+validateEncryption ::
+ SubGroup q =>
+ PublicKey q -> PublicKeyString ->
+ [Disjunction q] ->
+ (Encryption q, ValidityProof q) -> Bool
+validateEncryption pubKey zkp disjs (Encryption{..}, ValidityProof proofs) =
+       List.length disjs == List.length proofs &&
+       hash statement commitments == challengeSum
+       where
+       challengeSum = sum (proof_challenge <$> proofs)
+       statement =
+               "prove|"<>zkp<>"|"<>
+               fromString (show (intG encryption_nonce))<>","<>
+               fromString (show (intG encryption_vault))<>"|"
+       commitments = join $ List.zipWith commitment disjs proofs
+               where commitment disj Proof{..} =
+                       -- g = groupGen
+                       -- h = pubKey
+                       -- y1 = encryption_nonce
+                       -- y2 = (encryption_vault * disj)
+                       -- com1 = g^res / y1 ^ ch
+                       -- com2 = h^res / y2 ^ ch
+                       [ groupGen^proof_response / encryption_nonce       ^proof_challenge
+                       , pubKey  ^proof_response / (encryption_vault*disj)^proof_challenge
+                       ]
+
+-- * Type 'Question'
+data Question q = Question
+ {   question_text    :: Text
+ ,   question_answers :: [Text]
+ ,   question_min     :: E q
+ ,   question_max     :: E q
+ -- ,   question_blank :: Maybe Bool
+ } deriving (Eq, Show)
+
+-- * Type 'Answer'
+data Answer q = Answer
+ {   answer_opinions :: [(Encryption q, ValidityProof q)]
+     -- ^ Encrypted 'Opinion' for each 'question_answers'
+     -- with a 'ValidityProof' that they belong to [0,1].
+ ,   answer_sumProof :: ValidityProof q
+     -- ^ Proofs that the sum of the 'Opinon's encrypted in 'answer_opinions'
+     -- is an element of ['question_min'..'question_max'].
+ -- , answer_blankProof ::
+ }
+
+answer ::
+ forall m r q.
+ Monad m =>
+ RandomGen r =>
+ SubGroup q =>
+ PublicKey q -> PublicKeyString ->
+ Question q ->
+ [Opinion q] ->
+ S.StateT r m (Answer q)
+answer pubKey zkp Question{..} opinions = do
+       encryptions <- encrypt pubKey `mapM` opinions
+       individualProofs :: [ValidityProof q] <-
+               sequence $ List.zipWith
+                (validableEncryption pubKey zkp validBool)
+                opinions encryptions
+       sumProof <-
+               validableEncryption pubKey zkp
+                (validRange question_min question_max)
+                (sum opinions - question_min)
+                ( sum (fst <$> encryptions)
+                , sum (snd <$> encryptions) )
+       return Answer
+        { answer_opinions =
+               List.zip
+                (snd <$> encryptions) -- NOTE: drop the secretNonce
+                individualProofs
+        , answer_sumProof = sumProof
         }
 
--- | Prove that alpha = g^r and beta = y^r/d!!i
--- the size of d is the number of disjuncts
-elgamalDisjProve ::
- PublicKey q -> [G q] -> Text -> Int -> Random q -> CipherText q -> Proof q
-elgamalDisjProve y d zkp i r CipherText{..} =
-       undefined
-
-{-
-type Randomness
-type Message
-type Answer
-
-
--}
-{-
-  (** ZKPs for disjunctions *)
-
-  let eg_disj_prove y d zkp x r {alpha; beta} =
-    (* prove that alpha = g^r and beta = y^r/d_x *)
-    (* the size of d is the number of disjuncts *)
-    let n = Array.length d in
-    assert (0 <= x && x < n);
-    let proofs = Array.make n dummy_proof
-    and commitments = Array.make (2*n) g
-    and total_challenges = ref Z.zero in
-    (* compute fake proofs *)
-    let f i =
-      let challenge = random q
-      and response = random q in
-      challenge >>= fun challenge ->
-        response >>= fun response ->
-          proofs.(i)          <- {challenge; response};
-          commitments.(2*i)   <- g **~ response / alpha **~ challenge;
-          commitments.(2*i+1) <- y **~ response / (beta *~ d.(i)) **~ challenge;
-          total_challenges := Z.(!total_challenges + challenge);
-          return ()
-    in
-               -- Apply f to all elements of d except the xth
-    let rec loop i =
-      if i < x then f i >>= fun () -> loop (succ i)
-      else if i = x then loop (succ i)
-      else if i < n then f i >>= fun () -> loop (succ i)
-      else return ()
-    in loop 0 >>= fun () ->
-      total_challenges := Z.(q - !total_challenges mod q);
-      (* compute genuine proof *)
-      fs_prove [| g; y |] r (fun commitx ->
-        Array.blit commitx 0 commitments (2*x) 2;
-        let prefix = Printf.sprintf "prove|%s|%s,%s|"
-          zkp (G.to_string alpha) (G.to_string beta)
-        in
-        Z.((G.hash prefix commitments + !total_challenges) mod q)
-      ) >>= fun p ->
-        proofs.(x) <- p;
-        return proofs
--}
+validateAnswer ::
+ SubGroup q =>
+ PublicKey q -> PublicKeyString ->
+ Question q ->
+ Answer q -> Bool
+validateAnswer pubKey zkp Question{..} Answer{..} =
+       and (validateEncryption pubKey zkp validBool <$> answer_opinions) &&
+       validateEncryption pubKey zkp
+        (validRange question_min question_max)
+        ( sum (fst <$> answer_opinions)
+        , answer_sumProof )
index 359ae05af72ca4d646bc8c5d75591f8f1a1e902e..7c4345c0e2a8a1b3ef16d37e504066c8402b5e09 100644 (file)
@@ -18,9 +18,9 @@ hunit = testGroup "Election"
  [ testGroup "groupGenInverses" $
         [ testCase "WeakParams" $
                List.take 10 (groupGenInverses @WeakParams) @?=
-                       [groupGen^neg (inE i) | i <- [0..9]]
+                       [groupGen^neg (inE i) | i <- [0..9::Int]]
         , testCase "BeleniosParams" $
                List.take 10 (groupGenInverses @BeleniosParams) @?=
-                       [groupGen^neg (inE i) | i <- [0..9]]
+                       [groupGen^neg (inE i) | i <- [0..9::Int]]
         ]
  ]