1 {-# LANGUAGE NoMonomorphismRestriction #-}
2 {-# LANGUAGE OverloadedStrings #-}
3 {-# LANGUAGE StrictData #-}
4 {-# LANGUAGE TypeApplications #-}
5 {-# OPTIONS_GHC -Wno-missing-signatures #-}
6 module Hjugement.CLI.Trustee where
8 import Control.Applicative (Applicative(..))
9 import Control.Monad (Monad(..))
10 import Control.Monad.Trans.Maybe (MaybeT(..))
11 import Control.Monad.Trans.State.Strict (runState)
13 import Data.Eq (Eq(..))
14 import Data.Foldable (null)
15 import Data.Function (($), (.), id, flip)
16 import Data.Functor ((<$>))
17 import Data.Maybe (Maybe(..))
18 import Data.Ord (Ord(..))
19 import Data.Proxy (Proxy(..))
20 import Data.Semigroup (Semigroup(..))
21 import GHC.Prim (coerce)
23 import Symantic.CLI as CLI
24 import Text.Show (Show(..))
25 import System.IO (FilePath)
26 import qualified Data.List as List
27 import qualified Data.Text as T
28 import qualified Pipes as Pip
29 import qualified Pipes.Prelude as Pip
30 import qualified Symantic.Document as Doc
31 import qualified System.FilePath as FP
32 import qualified System.Random as Rand
33 import qualified Voting.Protocol as VP
35 import Hjugement.CLI.Utils
38 data Trustee_Params = Trustee_Params
39 { trustee_crypto :: VP.FFC
43 "Commands for a trustee."
50 api_trustee_generate <!>
55 run_trustee globParams =
57 run_trustee_generate globParams params :!:
58 run_trustee_decrypt globParams params
63 api_trustee_generate =
64 "Run by a trustee to generate a share of an election key.\
65 \ Such a share consists of a private key and a public key with a certificate.\
66 \ Generated files are stored in the current directory with\
67 \ a name that starts with "<>fileRef "ID"<>",\
68 \ where "<>fileRef "ID"<>" is a short fingerprint of the public key.\
69 \ The private key is stored in "<>fileRef "ID.privkey"<>" and must be\
70 \ secured by the trustee. The public key is stored in "<>fileRef "ID.pubkey"<>" and must\
71 \ be sent to the election administrator."
76 glob@Global_Params{..}
78 VP.reify trustee_crypto $ \(_crypto::Proxy c) -> do
79 (secKey, pubKey) <- Pip.liftIO $ Rand.getStdRandom $ runState $ do
80 secKey <- VP.randomSecretKey @c
81 pubKey <- VP.proveIndispensableTrusteePublicKey secKey
82 return (secKey, pubKey)
84 T.unpack $ T.toUpper $ T.take 8 $
85 VP.hexSHA256 $ VP.bytesNat $
86 VP.trustee_PublicKey pubKey
88 Pip.each [pubIdent] >-> pipeInfo glob (\ident ->
90 "generated trustee keypair "<>ident<>
91 " in "<>(global_dir FP.</> ident)<>".{privkey,pubkey}"
93 Pip.each [secKey] >-> writeJSON glob 0o400 (global_dir FP.</> pubIdent FP.<.>"privkey")
94 Pip.each [pubKey] >-> writeJSON glob 0o444 (global_dir FP.</> pubIdent FP.<.>"pubkey")
98 data TrusteeDecrypt_Params = TrusteeDecrypt_Params
99 { trusteeDecrypt_privkey :: FilePath
100 , trusteeDecrypt_url :: FilePath
103 api_trustee_decrypt =
104 "This command is run by each trustee to perform a partial decryption."
107 rule "TrusteeDecryptParams"
108 (TrusteeDecrypt_Params
109 <$> api_param_privkey
111 <?> response @(Maybe (VP.DecryptionShare ()))
114 "Read private key from file "<>ref"FILE"<>"."
116 requiredTag "privkey" (var "FILE")
119 glob@Global_Params{..}
121 TrusteeDecrypt_Params{..} =
122 VP.reify trustee_crypto $ \(_crypto::Proxy c) -> runMaybeT $ do
123 (secKey::VP.E c) <- loadJSON glob trusteeDecrypt_privkey
124 let pubKey = VP.publicKey secKey
125 let trusteeKeysPath = trusteeDecrypt_url FP.</> "public_keys.jsons"
126 outputInfo glob "check that the public key is amongst the public keys of the election"
127 keys <- runPipeWithError glob $
129 readJSON glob trusteeKeysPath
130 >-> Pip.filter ((pubKey ==) . VP.trustee_PublicKey)
132 () | null keys -> outputError glob $
133 "the public key associated with the given secret key "<>
134 "is not within the list of public trustee keys of the election.\n"<>
136 [ "List of trustees' public keys: "<>Doc.from trusteeKeysPath
137 , "Trustee's public key: "<>Doc.from (show (VP.nat pubKey))
139 () | List.length keys > 1 -> outputError glob $
140 "the public key associated with the given secret key "<>
141 "appears more than one time in the list of public trustee keys of the election.\n"<>
143 [ "List of trustees' public keys: "<>Doc.from trusteeKeysPath
144 , "Trustee's public key: "<>Doc.from (show (VP.nat pubKey))
147 outputInfo glob "tally the encrypted ballots"
148 -- FIXME: actually support fetching through an URL
149 let ballotsPath = trusteeDecrypt_url FP.</> "ballots.jsons"
150 (encTally, _numBallots) <- runPipeWithError glob $
152 (flip VP.insertEncryptedTally)
153 VP.emptyEncryptedTally id $
154 readJSON glob ballotsPath
155 decShare <- Pip.liftIO $
156 Rand.getStdRandom $ runState $
157 VP.proveDecryptionShare encTally secKey
158 return (coerce decShare :: VP.DecryptionShare ())