Commit 473597a2 authored by Stephane Glondu's avatar Stephane Glondu

Add "open" questions

parent 78783545
Pipeline #68036 passed with stages
in 17 minutes and 34 seconds
......@@ -12,6 +12,7 @@ all:
check: minimal
demo/demo.sh
demo/demo-threshold.sh
demo/demo-open.sh
clean:
-ocamlbuild -clean
......
#!/bin/bash
set -e
export BELENIOS_USE_URANDOM=1
BELENIOS=${BELENIOS:-$PWD}
belenios-tool () {
$BELENIOS/_build/belenios-tool "$@"
}
header () {
echo
echo "=-=-= $1 =-=-="
echo
}
header "Setup election"
UUID=`uuidgen`
echo "UUID of the election is $UUID"
DIR=$BELENIOS/demo/data/$UUID
mkdir $DIR
cd $DIR
# Common options
uuid="--uuid $UUID"
group="--group $BELENIOS/demo/groups/rfc3526-2048.json"
# Generate credentials
belenios-tool credgen $uuid $group --count 5
mv *.pubcreds public_creds.txt
mv *.privcreds private_creds.txt
# Generate trustee keys
belenios-tool trustee-keygen $group
belenios-tool trustee-keygen $group
belenios-tool trustee-keygen $group
cat *.pubkey > public_keys.jsons
# Generate election parameters
belenios-tool mkelection $uuid $group --template $BELENIOS/demo/templates/questions-open.json
header "Simulate votes"
cat > votes.txt <<EOF
[[1,0],[1,0,0],[1,2,3]]
[[1,0],[0,1,0],[6,5,4]]
[[0,1],[0,0,1],[7,9,8]]
[[1,0],[1,0,0],[11,10,12]]
[[0,0],[0,1,0],[15,13,14]]
EOF
paste private_creds.txt votes.txt | while read id cred vote; do
belenios-tool vote --privcred <(echo "$cred") --ballot <(echo "$vote")
echo "Voter $id voted" >&2
echo >&2
done > ballots.tmp
mv ballots.tmp ballots.jsons
header "Perform verification"
belenios-tool verify
header "Simulate and verify update"
tdir="$(mktemp -d)"
cp election.json public_creds.txt public_keys.jsons "$tdir"
head -n3 ballots.jsons > "$tdir/ballots.jsons"
belenios-tool verify-diff --dir1="$tdir" --dir2=.
rm -rf "$tdir"
header "Perform decryption"
for u in *.privkey; do
belenios-tool decrypt --privkey $u
echo >&2
done > partial_decryptions.tmp
mv partial_decryptions.tmp partial_decryptions.jsons
header "Finalize tally"
belenios-tool validate
header "Perform final verification"
belenios-tool verify
echo
echo "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="
echo
echo "The simulated election was successful! Its result can be seen in"
echo " $DIR/result.json"
echo
echo "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="
echo
{"g":"2","p":"32317006071311007300338913926423828248817941241140239112842009751400741706634354222619689417363569347117901737909704191754605873209195028853758986185622153212175412514901774520270235796078236248884246189477587641105928646099411723245426622522193230540919037680524235519125679715870117001058055877651038861847280257976054903569732561526167081339361799541336476559160368317896729073178384589680639671900977202194168647225871031411336429319536193471636533209717077448227988588565369208645296636077250268955505928362751121174096972998068410554359584866583291642136218231078990999448652468262416972035911852507045361090559","q":"16158503035655503650169456963211914124408970620570119556421004875700370853317177111309844708681784673558950868954852095877302936604597514426879493092811076606087706257450887260135117898039118124442123094738793820552964323049705861622713311261096615270459518840262117759562839857935058500529027938825519430923640128988027451784866280763083540669680899770668238279580184158948364536589192294840319835950488601097084323612935515705668214659768096735818266604858538724113994294282684604322648318038625134477752964181375560587048486499034205277179792433291645821068109115539495499724326234131208486017955926253522680545279"}
{"g":"2","p":"32317006071311007300338913926423828248817941241140239112842009751400741706634354222619689417363569347117901737909704191754605873209195028853758986185622153212175412514901774520270235796078236248884246189477587641105928646099411723245426622522193230540919037680524235519125679715870117001058055877651038861847280257976054903569732561526167081339361799541336476559160368317896729073178384589680639671900977202194168647225871031411336429319536193471636533209717077448227988588565369208645296636077250268955505928362751121174096972998068410554359584866583291642136218231078990999448652468262416972035911852507045361090559","q":"16158503035655503650169456963211914124408970620570119556421004875700370853317177111309844708681784673558950868954852095877302936604597514426879493092811076606087706257450887260135117898039118124442123094738793820552964323049705861622713311261096615270459518840262117759562839857935058500529027938825519430923640128988027451784866280763083540669680899770668238279580184158948364536589192294840319835950488601097084323612935515705668214659768096735818266604858538724113994294282684604322648318038625134477752964181375560587048486499034205277179792433291645821068109115539495499724326234131208486017955926253522680545279","embedding":{"padding":8,"bits_per_int":8}}
{"description":"Description of the election.","name":"Name of the election","questions":[{"answers":["Answer 1","Answer 2"],"min":0,"max":1,"question":"Question 1?"},{"answers":["Answer 1","Answer 2"],"blank":true,"min":1,"max":1,"question":"Question 2?"},{"type":"open","value":{"answers":["Answer 1","Answer 2","Answer 3"],"question":"Question 3?"}}]}
......@@ -111,15 +111,13 @@ module Make (W : ELECTION_DATA) (M : RANDOM) = struct
"sig|" ^ zkp ^ "|" ^ G.to_string commitment ^ "|"
let make_sig_contents answers =
List.flatten (
List.map (fun a ->
List.flatten (
List.map (fun {alpha; beta} ->
[alpha; beta]
) (Shape.flatten (Q.extract_ciphertexts a))
)
) (Array.to_list answers)
) |> Array.of_list
Array.map2 Q.extract_ciphertexts election.e_params.e_questions answers
|> Array.map Shape.flatten
|> Array.to_list
|> List.flatten
|> List.map (fun {alpha; beta} -> [alpha; beta])
|> List.flatten
|> Array.of_list
let create_ballot ?sk m =
let p = election.e_params in
......@@ -173,7 +171,7 @@ module Make (W : ELECTION_DATA) (M : RANDOM) = struct
Array.forall2 (verify_answer p.e_public_key zkp) p.e_questions b.answers
let process_ballots bs =
let bs = Array.map (fun b -> Array.map Q.extract_ciphertexts b.answers) bs in
let bs = Array.map (fun b -> Array.map2 Q.extract_ciphertexts election.e_params.e_questions b.answers) bs in
SArray (
Array.mapi (fun i q ->
match Question.neutral_shape q with
......
......@@ -25,12 +25,38 @@ open Serializable_core_t
type question =
| Standard of Question_std_t.question
| Open of Question_open_t.question
let read_question l b = Standard (Question_std_j.read_question l b)
let write_question b (Standard q) = Question_std_j.write_question b q
let read_question l b =
let x = Yojson.Safe.read_json l b in
match x with
| `Assoc o ->
(match List.assoc_opt "type" o with
| None ->
Standard (Question_std_j.question_of_string (Yojson.Safe.to_string x))
| Some (`String "open") ->
(match List.assoc_opt "value" o with
| None -> failwith "Question.read_question: value is missing"
| Some v -> Open (Question_open_j.question_of_string (Yojson.Safe.to_string v))
)
| Some _ ->
failwith "Question.read_question: unexpected type"
)
| _ -> failwith "Question.read_question: unexpected JSON value"
let write_question b = function
| Standard q -> Question_std_j.write_question b q
| Open q ->
let o = [
"type", `String "open";
"value", Yojson.Safe.from_string (Question_open_j.string_of_question q);
]
in
Yojson.Safe.write_json b (`Assoc o)
let neutral_shape = function
| Standard q -> Some (SArray (Array.make (Question_std.question_length q) (SAtomic ())))
| Open _ -> None
let erase_question = function
| Standard q ->
......@@ -42,6 +68,12 @@ let erase_question = function
q_max = q.q_max;
q_question = "";
}
| Open q ->
let open Question_open_t in
Open {
q_answers = Array.map (fun _ -> "") q.q_answers;
q_question = "";
}
module type S = sig
type elt
......@@ -50,7 +82,7 @@ module type S = sig
val create_answer : question -> public_key:elt -> prefix:string -> int array -> Yojson.Safe.json m
val verify_answer : question -> public_key:elt -> prefix:string -> Yojson.Safe.json -> bool
val extract_ciphertexts : Yojson.Safe.json -> elt ciphertext shape
val extract_ciphertexts : question -> Yojson.Safe.json -> elt ciphertext shape
val compute_result : num_tallied:int -> question -> elt shape -> int shape
val check_result : question -> elt shape -> int shape -> bool
......@@ -60,32 +92,60 @@ module Make (M : RANDOM) (G : GROUP) = struct
type elt = G.t
type 'a m = 'a M.t
let ( >>= ) = M.bind
module Q = Question_std.Make (M) (G)
let create_answer (Standard q) ~public_key ~prefix m =
Q.create_answer q ~public_key ~prefix m >>= fun answer ->
answer
|> Question_std_j.string_of_answer G.write
|> Yojson.Safe.from_string
|> M.return
let verify_answer (Standard q) ~public_key ~prefix a =
a
|> Yojson.Safe.to_string
|> Question_std_j.answer_of_string G.read
|> Q.verify_answer q ~public_key ~prefix
let extract_ciphertexts a =
a
|> Yojson.Safe.to_string
|> Question_std_j.answer_of_string G.read
|> Q.extract_ciphertexts
module QStandard = Question_std.Make (M) (G)
module QOpen = Question_open.Make (M) (G)
let create_answer q ~public_key ~prefix m =
match q with
| Standard q ->
QStandard.create_answer q ~public_key ~prefix m >>= fun answer ->
answer
|> Question_std_j.string_of_answer G.write
|> Yojson.Safe.from_string
|> M.return
| Open q ->
QOpen.create_answer q ~public_key ~prefix m >>= fun answer ->
answer
|> Question_open_j.string_of_answer G.write
|> Yojson.Safe.from_string
|> M.return
let verify_answer q ~public_key ~prefix a =
match q with
| Standard q ->
a
|> Yojson.Safe.to_string
|> Question_std_j.answer_of_string G.read
|> QStandard.verify_answer q ~public_key ~prefix
| Open q ->
a
|> Yojson.Safe.to_string
|> Question_open_j.answer_of_string G.read
|> QOpen.verify_answer q ~public_key ~prefix
let extract_ciphertexts q a =
match q with
| Standard _ ->
a
|> Yojson.Safe.to_string
|> Question_std_j.answer_of_string G.read
|> QStandard.extract_ciphertexts
| Open _ ->
a
|> Yojson.Safe.to_string
|> Question_open_j.answer_of_string G.read
|> QOpen.extract_ciphertexts
let compute_result ~num_tallied =
let compute_std = lazy (Q.compute_result ~num_tallied) in
fun (Standard q) x ->
Lazy.force compute_std q x
let compute_std = lazy (QStandard.compute_result ~num_tallied) in
fun q x ->
match q with
| Standard q -> Lazy.force compute_std q x
| Open q -> QOpen.compute_result q x
let check_result (Standard q) x r =
Q.check_result q x r
let check_result q x r =
match q with
| Standard q -> QStandard.check_result q x r
| Open q -> QOpen.check_result q x r
end
......@@ -25,6 +25,7 @@ open Serializable_core_t
type question =
| Standard of Question_std_t.question
| Open of Question_open_t.question
val read_question : Yojson.Safe.lexer_state -> Lexing.lexbuf -> question
val write_question : Bi_outbuf.t -> question -> unit
......@@ -39,7 +40,7 @@ module type S = sig
val create_answer : question -> public_key:elt -> prefix:string -> int array -> Yojson.Safe.json m
val verify_answer : question -> public_key:elt -> prefix:string -> Yojson.Safe.json -> bool
val extract_ciphertexts : Yojson.Safe.json -> elt ciphertext shape
val extract_ciphertexts : question -> Yojson.Safe.json -> elt ciphertext shape
val compute_result : num_tallied:int -> question -> elt shape -> int shape
val check_result : question -> elt shape -> int shape -> bool
......
(**************************************************************************)
(* BELENIOS *)
(* *)
(* Copyright © 2012-2019 Inria *)
(* *)
(* This program is free software: you can redistribute it and/or modify *)
(* it under the terms of the GNU Affero General Public License as *)
(* published by the Free Software Foundation, either version 3 of the *)
(* License, or (at your option) any later version, with the additional *)
(* exemption that compiling, linking, and/or using OpenSSL is allowed. *)
(* *)
(* This program is distributed in the hope that it will be useful, but *)
(* WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *)
(* Affero General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU Affero General Public *)
(* License along with this program. If not, see *)
(* <http://www.gnu.org/licenses/>. *)
(**************************************************************************)
<doc text="Serializable datatypes for open questions">
(** {2 Predefined types} *)
type 'a ciphertext <ocaml predef from="Serializable_core"> = abstract
type proof <ocaml predef from="Serializable_core"> = abstract
(** {2 Questions and answers} *)
type question = {
answers : string list <ocaml repr="array">;
question : string;
} <ocaml field_prefix="q_">
type 'a answer = {
choices : 'a ciphertext;
proof : proof;
}
<doc text="An answer to a question.">
(**************************************************************************)
(* BELENIOS *)
(* *)
(* Copyright © 2012-2019 Inria *)
(* *)
(* This program is free software: you can redistribute it and/or modify *)
(* it under the terms of the GNU Affero General Public License as *)
(* published by the Free Software Foundation, either version 3 of the *)
(* License, or (at your option) any later version, with the additional *)
(* exemption that compiling, linking, and/or using OpenSSL is allowed. *)
(* *)
(* This program is distributed in the hope that it will be useful, but *)
(* WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *)
(* Affero General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU Affero General Public *)
(* License along with this program. If not, see *)
(* <http://www.gnu.org/licenses/>. *)
(**************************************************************************)
open Platform
open Signatures_core
open Serializable_builtin_t
open Serializable_core_t
open Question_open_t
let check_modulo p x = Z.(geq x zero && lt x p)
module type S = sig
type elt
type 'a m
val create_answer : question -> public_key:elt -> prefix:string -> int array -> elt answer m
val verify_answer : question -> public_key:elt -> prefix:string -> elt answer -> bool
val extract_ciphertexts : elt answer -> elt ciphertext shape
val compute_result : question -> elt shape -> int shape
val check_result : question -> elt shape -> int shape -> bool
end
module Make (M : RANDOM) (G : GROUP) = struct
type elt = G.t
type 'a m = 'a M.t
let ( >>= ) = M.bind
open G
let create_answer q ~public_key:y ~prefix m =
assert (Array.length q.q_answers = Array.length m);
M.random G.q >>= fun r ->
let alpha = g **~ r and beta = (y **~ r) *~ (G.of_ints m) in
M.random G.q >>= fun w ->
let commitment = g **~ w in
let zkp = Printf.sprintf "raweg|%s|%s,%s,%s|" prefix (G.to_string y) (G.to_string alpha) (G.to_string beta) in
let challenge = G.hash zkp [| commitment |] in
let response = Z.(erem (w - r * challenge) G.q) in
let proof = {challenge; response} in
let choices = {alpha; beta} in
M.return {choices; proof}
let verify_answer _ ~public_key:y ~prefix a =
let {alpha; beta} = a.choices in
let {challenge; response} = a.proof in
G.check alpha && G.check beta &&
check_modulo G.q challenge && check_modulo G.q response &&
let commitment = (g **~ response) *~ (alpha **~ challenge) in
let zkp = Printf.sprintf "raweg|%s|%s,%s,%s|" prefix (G.to_string y) (G.to_string alpha) (G.to_string beta) in
Z.(challenge =% G.hash zkp [| commitment |])
let extract_ciphertexts a =
SAtomic a.choices
let compute_result q x =
let n = Array.length q.q_answers in
let rec aux = function
| SAtomic x -> SArray (Array.map (fun x -> SAtomic x) (G.to_ints n x))
| SArray xs -> SArray (Array.map aux xs)
in aux x
let check_result q x r =
r = compute_result q x
end
(**************************************************************************)
(* BELENIOS *)
(* *)
(* Copyright © 2012-2019 Inria *)
(* *)
(* This program is free software: you can redistribute it and/or modify *)
(* it under the terms of the GNU Affero General Public License as *)
(* published by the Free Software Foundation, either version 3 of the *)
(* License, or (at your option) any later version, with the additional *)
(* exemption that compiling, linking, and/or using OpenSSL is allowed. *)
(* *)
(* This program is distributed in the hope that it will be useful, but *)
(* WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *)
(* Affero General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU Affero General Public *)
(* License along with this program. If not, see *)
(* <http://www.gnu.org/licenses/>. *)
(**************************************************************************)
open Signatures_core
open Serializable_builtin_t
open Question_open_t
module type S = sig
type elt
type 'a m
val create_answer : question -> public_key:elt -> prefix:string -> int array -> elt answer m
val verify_answer : question -> public_key:elt -> prefix:string -> elt answer -> bool
val extract_ciphertexts : elt answer -> elt ciphertext shape
val compute_result : question -> elt shape -> int shape
val check_result : question -> elt shape -> int shape -> bool
end
module Make (M : RANDOM) (G : GROUP) : S with type 'a m = 'a M.t and type elt = G.t
......@@ -5,6 +5,8 @@ Serializable_builtin_j
Serializable_core_j
Question_std_j
Question_std
Question_open_j
Question_open
Question
Serializable_j
Common
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment