Commit e96d4387 authored by Stephane Glondu's avatar Stephane Glondu

Add support for threshold decryption (backend)

 - no encryption for now
 - no support in belenios-tool.html
parent f366ac80
......@@ -6,6 +6,7 @@ all:
check: all
demo/demo.sh
demo/demo-threshold.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/default.json"
# Generate credentials
belenios-tool credgen $uuid $group --count 5
mv *.pubcreds public_creds.txt
mv *.privcreds private_creds.txt
# Generate trustee keys
ttkeygen () {
belenios-tool threshold-trustee-keygen $group "$@"
}
ttkeygen --step 1
ttkeygen --step 1
ttkeygen --step 1
cat *.cert > certs.jsons
ttkeygen --certs certs.jsons --step 2
for u in *.key; do
ttkeygen --certs certs.jsons --key $u --step 3 --threshold 2
done > polynomials.jsons
ttkeygen --certs certs.jsons --step 4 --polynomials polynomials.jsons
for u in *.key; do
b=${u%.key}
ttkeygen --certs certs.jsons --key $u --step 5 < $b.vinput > $b.voutput
done
cat *.voutput | ttkeygen --certs certs.jsons --step 6 --polynomials polynomials.jsons > threshold.json
# Generate election parameters
belenios-tool mkelection $uuid $group --template $BELENIOS/demo/templates/questions.json
header "Simulate votes"
cat > votes.txt <<EOF
[[1,0],[1,0,0]]
[[1,0],[0,1,0]]
[[0,1],[0,0,1]]
[[1,0],[1,0,0]]
[[0,0],[0,1,0]]
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 threshold.json public_creds.txt "$tdir"
head -n3 ballots.jsons > "$tdir/ballots.jsons"
belenios-tool verify-diff --dir1="$tdir" --dir2=.
rm -rf "$tdir"
header "Perform decryption"
for u in *.key; do
belenios-tool threshold-decrypt --key $u --decryption-key ${u%.key}.dkey
echo >&2
done > partial_decryptions.tmp
head -n2 partial_decryptions.tmp > partial_decryptions.jsons
header "Finalize tally"
belenios-tool finalize
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
......@@ -123,6 +123,17 @@ module Array = struct
let ssplit a =
mmap fst a, mmap snd a
let findi f a =
let n = Array.length a in
let rec loop i =
if i < n then
match f i a.(i) with
| None -> loop (i+1)
| Some _ as x -> x
else None
in loop 0
end
module String = struct
......
......@@ -40,6 +40,7 @@ module Array : sig
val mmap3 : ('a -> 'b -> 'c -> 'd) ->
'a array array -> 'b array array -> 'c array array -> 'd array array
val ssplit : ('a * 'b) array array -> 'a array array * 'b array array
val findi : (int -> 'a -> 'b option) -> 'a array -> 'b option
end
module String : sig
......
......@@ -563,11 +563,10 @@ module MakeElection (G : GROUP) (M : RANDOM) = struct
type result = elt Serializable_t.result
let combine_factors num_tallied encrypted_tally partial_decryptions =
let dummy = Array.mmap (fun _ -> G.one) encrypted_tally in
let factors = Array.fold_left (fun a b ->
Array.mmap2 ( *~ ) a b.decryption_factors
) dummy partial_decryptions in
type combinator = factor array -> elt array array
let combine_factors num_tallied encrypted_tally partial_decryptions combinator =
let factors = combinator partial_decryptions in
let results = Array.mmap2 (fun {beta; _} f ->
beta / f
) encrypted_tally factors in
......@@ -588,17 +587,14 @@ module MakeElection (G : GROUP) (M : RANDOM) = struct
let result = Array.mmap log results in
{num_tallied; encrypted_tally; partial_decryptions; result}
let check_result pks r =
let check_result combinator pks r =
let {encrypted_tally; partial_decryptions; result; _} = r in
check_ciphertext encrypted_tally &&
(* decryption factors may be not in the same order as pks! *)
Array.forall (fun pk ->
Array.exists (check_factor encrypted_tally pk) partial_decryptions
) pks &&
let dummy = Array.mmap (fun _ -> G.one) encrypted_tally in
let factors = Array.fold_left (fun a b ->
Array.mmap2 ( *~ ) a b.decryption_factors
) dummy partial_decryptions in
Array.forall (fun pd ->
Array.exists (fun pk -> check_factor encrypted_tally pk pd) pks
) partial_decryptions &&
let factors = combinator partial_decryptions in
let results = Array.mmap2 (fun {beta; _} f ->
beta / f
) encrypted_tally factors in
......
......@@ -128,3 +128,71 @@ type 'a result = {
partial_decryptions : 'a partial_decryption list <ocaml repr="array">;
result : plaintext;
}
(** {2 Channel messages support} *)
type 'a raw_channel_msg = {
recipient : 'a;
message : string;
} <ocaml field_prefix="raw_">
type channel_msg = {
message : string; (* raw_channel_msg *)
signature : proof;
} <ocaml field_prefix="channel_">
(** {2 Threshold decryption support} *)
type 'a cert_keys = {
verification : 'a;
encryption : 'a;
} <ocaml field_prefix="cert_">
type cert = {
keys : string; (* cert_keys *)
signature : proof;
} <ocaml field_prefix="cert_">
type certs = {
certs : cert list <ocaml repr="array">;
}
type raw_polynomial = number list <ocaml repr="array">
type 'a raw_coefexps = 'a list <ocaml repr="array">
type coefexps = {
coefexps : string; (* raw_coefexps *)
signature : proof;
} <ocaml field_prefix="ce_">
type secret = {
secret : number;
}
type polynomial = {
polynomial : string; (* sent raw_polynomial *)
secrets : string list <ocaml repr="array">; (* sent secrets *)
coefexps : coefexps;
} <ocaml field_prefix="p_">
type vinput = {
polynomial : string; (* sent raw_polynomial *)
secrets : string list <ocaml repr="array">; (* sent secrets *)
coefexps : coefexps list <ocaml repr="array">;
} <ocaml field_prefix="vi_">
type partial_decryption_key = {
decryption_key : number;
} <ocaml field_prefix="pdk_">
type 'a voutput = {
private_key : string; (* sent partial_decryption_key *)
public_key : 'a trustee_public_key;
} <ocaml field_prefix="vo_">
type 'a threshold_parameters = {
threshold : int;
certs : cert list <ocaml repr="array">;
coefexps : coefexps list <ocaml repr="array">;
verification_keys : 'a trustee_public_key list <ocaml repr="array">;
} <ocaml field_prefix="t_">
......@@ -223,13 +223,56 @@ module type ELECTION = sig
(** The election result. It contains the needed data to validate the
result from the encrypted tally. *)
val combine_factors : int -> ciphertext -> factor array -> result
type combinator = factor array -> elt array array
val combine_factors : int -> ciphertext -> factor array -> combinator -> result
(** Combine the encrypted tally and the factors from all trustees to
produce the election result. The first argument is the number of
tallied ballots. May raise [Invalid_argument]. *)
val check_result : public_key array -> result -> bool
val check_result : combinator -> public_key array -> result -> bool
val extract_tally : result -> plaintext
(** Extract the plaintext result of the election. *)
end
module type PKI = sig
type 'a m
type private_key
type public_key
val genkey : unit -> string m
val derive_sk : string -> private_key
val derive_dk : string -> private_key
val sign : private_key -> string -> proof m
val verify : public_key -> string -> proof -> bool
val encrypt : public_key -> string -> string m
val decrypt : private_key -> string -> string
val make_cert : sk:private_key -> dk:private_key -> cert m
val verify_cert : cert -> bool
end
module type CHANNELS = sig
type 'a m
type private_key
type public_key
val send : private_key -> public_key -> string -> string m
val recv : private_key -> public_key -> string -> string
end
module type PEDERSEN = sig
type 'a m
type elt
val step1 : unit -> (string * cert) m
val step2 : certs -> unit
val step3 : certs -> string -> int -> polynomial m
val step4 : certs -> polynomial array -> vinput array
val step5 : certs -> string -> vinput -> elt voutput m
val step6 : certs -> polynomial array -> elt voutput array -> elt threshold_parameters
val check : elt threshold_parameters -> bool
val combine : elt threshold_parameters -> elt
type checker = elt -> elt partial_decryption -> bool
val combine_factors : checker -> elt threshold_parameters -> elt partial_decryption array -> elt array array
end
This diff is collapsed.
......@@ -43,5 +43,32 @@ module MakeSimpleDistKeyGen (G : GROUP) (M : RANDOM) : sig
val combine : G.t trustee_public_key array -> G.t
(** Combine all public key shares into an election public key. *)
val combine_factors : G.t partial_decryption array -> G.t array array
end
(** Simple distributed generation of an election public key. *)
module MakePKI (G : GROUP) (M : RANDOM) :
PKI with type 'a m = 'a M.t
and type private_key = Z.t
and type public_key = G.t
module MakeChannels (G : GROUP) (M : RANDOM)
(P : PKI with type 'a m = 'a M.t
and type private_key = Z.t
and type public_key = G.t) :
CHANNELS with type 'a m = 'a P.m
and type private_key = P.private_key
and type public_key = P.public_key
exception PedersenFailure of string
module MakePedersen (G : GROUP) (M : RANDOM)
(P : PKI with type 'a m = 'a M.t
and type private_key = Z.t
and type public_key = G.t)
(C : CHANNELS with type 'a m = 'a M.t
and type private_key = Z.t
and type public_key = G.t) :
PEDERSEN with type 'a m = 'a M.t
and type elt = G.t
This diff is collapsed.
......@@ -27,6 +27,7 @@ open Common
module type PARAMS = sig
val election : string
val get_public_keys : unit -> string array option
val get_threshold : unit -> string option
val get_public_creds : unit -> string Stream.t option
val get_ballots : unit -> string Stream.t option
val get_result : unit -> string option
......@@ -36,6 +37,7 @@ end
module type S = sig
val vote : string option -> int array array -> string
val decrypt : string -> string
val tdecrypt : string -> string -> string
val finalize : string array -> string
val verify : unit -> unit
end
......@@ -60,32 +62,42 @@ module Make (P : PARSED_PARAMS) : S = struct
module M = Election.MakeSimpleMonad(G)
module E = Election.MakeElection(G)(M);;
module KG = Trustees.MakeSimpleDistKeyGen (G) (M)
module P = Trustees.MakePKI (G) (M)
module C = Trustees.MakeChannels (G) (M) (P)
module KP = Trustees.MakePedersen (G) (M) (P) (C)
(* Load and check trustee keys, if present *)
module KG = Trustees.MakeSimpleDistKeyGen(G)(M);;
let threshold =
match get_threshold () with
| None -> None
| Some x -> Some (threshold_parameters_of_string G.read x)
let public_keys_with_pok =
match threshold with
| None ->
get_public_keys () |> option_map @@
Array.map (trustee_public_key_of_string G.read)
| Some t -> Some t.t_verification_keys
let () =
match public_keys_with_pok with
| Some pks ->
match public_keys_with_pok, threshold with
| Some pks, None ->
assert (Array.forall KG.check pks);
let y' = KG.combine pks in
assert G.(election.e_params.e_public_key =~ y')
| None -> ()
| _ -> ()
let public_keys =
option_map (
Array.map (fun pk -> pk.trustee_public_key)
) public_keys_with_pok
(* Finish setting up the election *)
let pks = match public_keys with
let pks = lazy (match public_keys with
| Some pks -> pks
| None -> failwith "missing public keys"
| None -> failwith "missing public keys")
(* Load ballots, if present *)
......@@ -155,7 +167,7 @@ module Make (P : PARSED_PARAMS) : S = struct
let decrypt privkey =
let sk = number_of_string privkey in
let pk = G.(g **~ sk) in
if Array.forall (fun x -> not G.(x =~ pk)) pks then (
if Array.forall (fun x -> not G.(x =~ pk)) (Lazy.force pks) then (
print_msg "W: your key is not present in public_keys.jsons";
);
let tally = Lazy.force encrypted_tally in
......@@ -163,15 +175,47 @@ module Make (P : PARSED_PARAMS) : S = struct
assert (E.check_factor tally pk factor);
string_of_partial_decryption G.write factor
let tdecrypt key pdk =
let sk = P.derive_sk key and dk = P.derive_dk key in
let vk = G.(g **~ sk) in
let pdk = C.recv dk vk pdk in
let pdk = (partial_decryption_key_of_string pdk).pdk_decryption_key in
let pvk = G.(g **~ pdk) in
(match threshold with
| None -> print_msg "W: threshold parameters are missing"
| Some t ->
if Array.forall (fun x ->
not G.(x.trustee_public_key =~ pvk)
) t.t_verification_keys then
print_msg "W: your key is not present in threshold parameters"
);
let tally = Lazy.force encrypted_tally in
let factor = E.compute_factor tally pdk () in
assert (E.check_factor tally pvk factor);
string_of_partial_decryption G.write factor
let finalize factors =
let factors = Array.map (partial_decryption_of_string G.read) factors in
let tally = Lazy.force encrypted_tally in
assert (Array.forall2 (E.check_factor tally) pks factors);
let result = E.combine_factors (M.cardinal ()) tally factors in
assert (E.check_result pks result);
let checker = E.check_factor tally in
let combinator =
match threshold with
| None ->
assert (Array.forall2 checker (Lazy.force pks) factors);
KG.combine_factors
| Some t -> KP.combine_factors checker t
in
let result = E.combine_factors (M.cardinal ()) tally factors combinator in
assert (E.check_result combinator (Lazy.force pks) result);
string_of_result G.write result
let verify () =
(match threshold with
| Some t ->
assert (KP.check t);
assert G.(election.e_params.e_public_key =~ KP.combine t)
| None -> ignore (Lazy.force pks)
);
(match Lazy.force ballots_check with
| Some () -> ()
| None -> print_msg "W: no ballots to check"
......@@ -180,7 +224,11 @@ module Make (P : PARSED_PARAMS) : S = struct
| Some result ->
let result = result_of_string G.read result in
assert (Lazy.force encrypted_tally = result.encrypted_tally);
assert (E.check_result pks result)
let combinator = match threshold with
| None -> KG.combine_factors
| Some t -> KP.combine_factors (E.check_factor result.encrypted_tally) t
in
assert (E.check_result combinator (Lazy.force pks) result)
| None -> print_msg "W: no result to check"
);
print_msg "I: all checks passed"
......
module type PARAMS = sig
val election : string
val get_public_keys : unit -> string array option
val get_threshold : unit -> string option
val get_public_creds : unit -> string Stream.t option
val get_ballots : unit -> string Stream.t option
val get_result : unit -> string option
......@@ -10,6 +11,7 @@ end
module type S = sig
val vote : string option -> int array array -> string
val decrypt : string -> string
val tdecrypt : string -> string -> string
val finalize : string array -> string
val verify : unit -> unit
end
......
......@@ -166,6 +166,7 @@ module Mkelection = struct
let template = get_textarea "mkelection_template"
let get_public_keys () =
Some (get_textarea "mkelection_pks" |> split_lines |> Array.of_list)
let get_threshold () = None
end in
let module X = (val make (module P : PARAMS) : S) in
set_textarea "mkelection_output" (X.mkelection ())
......@@ -185,6 +186,8 @@ module ToolElection = struct
let pks = Array.of_list raw in
if Array.length pks = 0 then None else Some pks
let get_threshold () = None
let get_public_creds () =
let raw = get_textarea "election_pubcreds" |> split_lines in
match raw with
......
......@@ -28,6 +28,7 @@ module type PARAMS = sig
val group : string
val template : string
val get_public_keys : unit -> string array option
val get_threshold : unit -> string option
end
module type S = sig
......@@ -39,6 +40,7 @@ module type PARSED_PARAMS = sig
val template : template
module G : GROUP
val get_public_keys : unit -> G.t trustee_public_key array option
val get_threshold : unit -> G.t threshold_parameters option
end
let parse_params p =
......@@ -54,6 +56,10 @@ let parse_params p =
match P.get_public_keys () with
| None -> None
| Some xs -> Some (Array.map (trustee_public_key_of_string G.read) xs)
let get_threshold () =
match P.get_threshold () with
| None -> None
| Some t -> Some (threshold_parameters_of_string G.read t)
end
in (module R : PARSED_PARAMS)
......@@ -66,14 +72,21 @@ module Make (P : PARSED_PARAMS) : S = struct
(* Setup trustees *)
module KG = Trustees.MakeSimpleDistKeyGen(G)(M);;
let y =
match get_threshold () with
| None ->
let public_keys =
match get_public_keys () with
| Some keys -> keys
| None -> failwith "trustee keys are missing"
let y = KG.combine public_keys
in
let module K = Trustees.MakeSimpleDistKeyGen (G) (M) in
K.combine public_keys
| Some t ->
let module P = Trustees.MakePKI (G) (M) in
let module C = Trustees.MakeChannels (G) (M) (P) in
let module K = Trustees.MakePedersen (G) (M) (P) (C) in
K.combine t
(* Setup election *)
......
......@@ -3,6 +3,7 @@ module type PARAMS = sig
val group : string
val template : string
val get_public_keys : unit -> string array option
val get_threshold : unit -> string option
end
module type S = sig
......
......@@ -37,6 +37,9 @@ let lines_of_file fname =
let string_of_file f =
lines_of_file f |> stream_to_list |> String.concat "\n"
let string_of_file_opt filename =
if Sys.file_exists filename then Some (string_of_file filename) else None
let load_from_file of_string filename =
if Sys.file_exists filename then (
Some (lines_of_file filename |> stream_to_list |> List.rev_map of_string)
......@@ -49,7 +52,9 @@ type verifydiff_error =
| PublicKeysMismatch
| MissingPublicKeys
| InvalidPublicKeys
| InvalidThreshold
| PublicKeyMismatch
| ThresholdMismatch
| MissingCredentials
| InvalidCredential
| CredentialsMismatch
......@@ -67,7 +72,9 @@ let explain_error = function
| PublicKeysMismatch -> "public keys mismatch"
| MissingPublicKeys -> "missing public keys"
| InvalidPublicKeys -> "invalid public keys"
| InvalidThreshold -> "invalid threshold parameters"
| PublicKeyMismatch -> "public key mismatch"
| ThresholdMismatch -> "threshold parameters mismatch"
| MissingCredentials -> "missing credentials"
| InvalidCredential -> "invalid credential"
| CredentialsMismatch -> "credentials mismatch"
......@@ -96,22 +103,38 @@ let verifydiff dir1 dir2 =
let pks2 = load_from_file (fun x -> x) (dir2 / "public_keys.jsons") in
if pks2 <> pks then raise (VerifydiffError PublicKeysMismatch)
in
(* the public keys must be valid *)
(* the threshold parameters must be the same *)
let threshold = string_of_file_opt (dir1 / "threshold.json") in
let () =
let t2 = string_of_file_opt (dir2 / "threshold.json") in
if t2 <> threshold then raise (VerifydiffError ThresholdMismatch)
in
(* the public keys / threshold parameters must be valid *)
let module ED = (val Group.election_params_of_string election) in
let open ED in
let module M = Election.MakeSimpleMonad (G) in
let module E = Election.MakeElection (G) (M) in
let module KG = Trustees.MakeSimpleDistKeyGen (G) (M) in
let y =
match threshold with
| None ->
let module K = Trustees.MakeSimpleDistKeyGen (G) (M) in
let pks = match pks with
| None -> raise (VerifydiffError MissingPublicKeys)
| Some pks -> List.map (trustee_public_key_of_string G.read) pks
in
let () =
if not (List.for_all KG.check pks) then
raise (VerifydiffError InvalidPublicKeys)
if not (List.for_all K.check pks) then
raise (VerifydiffError InvalidPublicKeys);
K.combine (Array.of_list pks)
| Some t ->
let t = threshold_parameters_of_string G.read t in
let module P = Trustees.MakePKI (G) (M) in
let module C = Trustees.MakeChannels (G) (M) (P) in
let module K = Trustees.MakePedersen (G) (M) (P) (C) in
if not (K.check t) then
raise (VerifydiffError InvalidThreshold);
K.combine t
in
(* the public keys must correspond to the public key of election *)
let y = KG.combine (Array.of_list pks) in
let () =
if not G.(election.e_params.e_public_key =~ y) then
raise (VerifydiffError PublicKeyMismatch)
......
......@@ -24,7 +24,9 @@ type verifydiff_error =
| PublicKeysMismatch
| MissingPublicKeys
| InvalidPublicKeys
| InvalidThreshold
| PublicKeyMismatch
| ThresholdMismatch
| MissingCredentials
| InvalidCredential
| CredentialsMismatch
......
......@@ -1378,6 +1378,7 @@ let handle_election_tally_release (uuid, ()) () =
let%lwt metadata = Web_persist.get_election_metadata uuid_s in
let module W = (val w) in
let module E = Election.MakeElection (W.G) (LwtRandom) in
let module KG = Trustees.MakeSimpleDistKeyGen (W.G) (LwtRandom) in
let%lwt () =
match%lwt Web_state.get_site_user () with
| Some u when metadata.e_owner = Some u -> return ()
......@@ -1401,7 +1402,7 @@ let handle_election_tally_release (uuid, ()) () =
Lwt_io.chars_of_file |> Lwt_stream.to_string >>=
wrap1 (encrypted_tally_of_string W.G.read)
in
let result = E.combine_factors ntallied et pds in
let result = E.combine_factors ntallied et pds KG.combine_factors in
let%lwt () =
let open Lwt_io in
with_file
......
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