diff --git a/.config/dictionaries/project.dic b/.config/dictionaries/project.dic index 9580278cffa..1925830991e 100644 --- a/.config/dictionaries/project.dic +++ b/.config/dictionaries/project.dic @@ -29,6 +29,7 @@ CBOR cbork cdylib CEST +chacha CHAINCODE chainsync childs diff --git a/rust/catalyst-voting/Cargo.toml b/rust/catalyst-voting/Cargo.toml index df6e2aa051c..ce8c106b76b 100644 --- a/rust/catalyst-voting/Cargo.toml +++ b/rust/catalyst-voting/Cargo.toml @@ -13,7 +13,9 @@ workspace = true [dependencies] anyhow = "1.0.89" rand_core = "0.6.4" -curve25519-dalek = { version = "4.1.3", features = ["digest"] } +rand_chacha = "0.3.1" +curve25519-dalek = { version = "4.1.3", features = ["digest", "rand_core"] } +ed25519-dalek = { version = "2.1.1", features = ["rand_core"] } blake2b_simd = "1.0.2" [dev-dependencies] diff --git a/rust/catalyst-voting/src/crypto/ed25519/decoding.rs b/rust/catalyst-voting/src/crypto/ed25519/decoding.rs new file mode 100644 index 00000000000..ab4b5daa723 --- /dev/null +++ b/rust/catalyst-voting/src/crypto/ed25519/decoding.rs @@ -0,0 +1,42 @@ +//! `Ed25519` objects decoding implementation + +use ed25519_dalek::{ + Signature as Ed25519Signature, VerifyingKey, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, +}; + +use super::{PublicKey, Signature}; + +impl PublicKey { + /// `PublicKey` bytes size + pub const BYTES_SIZE: usize = PUBLIC_KEY_LENGTH; + + /// Convert this `PublicKey` to its underlying sequence of bytes. + #[must_use] + pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { + self.0.to_bytes() + } + + /// Attempt to construct a `PublicKey` from a byte representation. + /// + /// # Errors + /// - Cannot decode public key. + pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result { + Ok(Self(VerifyingKey::from_bytes(bytes)?)) + } +} + +impl Signature { + /// `Signature` bytes size + pub const BYTES_SIZE: usize = SIGNATURE_LENGTH; + + /// Convert this `Signature` to its underlying sequence of bytes. + #[must_use] + pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { + self.0.to_bytes() + } + + /// Attempt to construct a `Signature` from a byte representation. + pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> Self { + Self(Ed25519Signature::from_bytes(bytes)) + } +} diff --git a/rust/catalyst-voting/src/crypto/ed25519/mod.rs b/rust/catalyst-voting/src/crypto/ed25519/mod.rs new file mode 100644 index 00000000000..a9cc4488e54 --- /dev/null +++ b/rust/catalyst-voting/src/crypto/ed25519/mod.rs @@ -0,0 +1,72 @@ +//! `EdDSA` digital signature scheme over Curve25519. + +mod decoding; + +use ed25519_dalek::{ + ed25519::signature::Signer, Signature as Ed25519Signature, SigningKey, VerifyingKey, +}; +use rand_core::CryptoRngCore; + +/// `Ed25519` private key struct. +#[must_use] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PrivateKey(SigningKey); + +impl PrivateKey { + /// Randomly generate the `Ed25519` private key. + pub fn random(rng: &mut R) -> Self { + Self(SigningKey::generate(rng)) + } + + /// Get associated `Ed25519` public key. + pub fn public_key(&self) -> PublicKey { + PublicKey(self.0.verifying_key()) + } +} + +/// `Ed25519` public key struct. +#[must_use] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PublicKey(VerifyingKey); + +/// `Ed25519` signature struct. +#[must_use] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Signature(Ed25519Signature); + +/// Sign a message using the `Ed25519` private key. +pub fn sign(sk: &PrivateKey, msg: &[u8]) -> Signature { + Signature(sk.0.sign(msg)) +} + +/// Verify a `Ed25519` signature using the `Ed25519` public key. +#[must_use] +pub fn verify_signature(pk: &PublicKey, msg: &[u8], sig: &Signature) -> bool { + pk.0.verify_strict(msg, &sig.0).is_ok() +} + +#[cfg(test)] +mod tests { + use proptest::prelude::{any, Arbitrary, BoxedStrategy, Strategy}; + use test_strategy::proptest; + + use super::*; + + impl Arbitrary for PrivateKey { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + any::<[u8; 32]>() + .prop_map(|b| PrivateKey(SigningKey::from_bytes(&b))) + .boxed() + } + } + + #[proptest] + fn sign_test(private_key: PrivateKey, msg: Vec) { + let public_key = private_key.public_key(); + let signature = sign(&private_key, &msg); + assert!(verify_signature(&public_key, &msg, &signature)); + } +} diff --git a/rust/catalyst-voting/src/crypto/elgamal/decoding.rs b/rust/catalyst-voting/src/crypto/elgamal/decoding.rs index e2d82309cef..52e207f84c3 100644 --- a/rust/catalyst-voting/src/crypto/elgamal/decoding.rs +++ b/rust/catalyst-voting/src/crypto/elgamal/decoding.rs @@ -2,51 +2,14 @@ use anyhow::anyhow; -use super::{Ciphertext, GroupElement, PublicKey, Scalar, SecretKey}; - -impl PublicKey { - /// `PublicKey` bytes size - pub const BYTES_SIZE: usize = GroupElement::BYTES_SIZE; - - /// Convert this `PublicKey` to its underlying sequence of bytes. - #[must_use] - pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { - self.0.to_bytes() - } - - /// Attempt to construct a `PublicKey` from a byte representation. - /// - /// # Errors - /// - Cannot decode group element field. - pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result { - GroupElement::from_bytes(bytes).map(Self) - } -} - -impl SecretKey { - /// `SecretKey` bytes size - pub const BYTES_SIZE: usize = Scalar::BYTES_SIZE; - - /// Convert this `SecretKey` to its underlying sequence of bytes. - #[must_use] - pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { - self.0.to_bytes() - } - - /// Attempt to construct a `SecretKey` from a byte representation. - /// - /// # Errors - /// - Cannot decode scalar field. - pub fn from_bytes(bytes: [u8; Self::BYTES_SIZE]) -> anyhow::Result { - Scalar::from_bytes(bytes).map(Self) - } -} +use super::{Ciphertext, GroupElement}; impl Ciphertext { /// `Ciphertext` bytes size pub const BYTES_SIZE: usize = GroupElement::BYTES_SIZE * 2; /// Convert this `Ciphertext` to its underlying sequence of bytes. + #[must_use] pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { let mut res = [0; Self::BYTES_SIZE]; res[0..32].copy_from_slice(&self.0.to_bytes()); @@ -58,6 +21,8 @@ impl Ciphertext { /// /// # Errors /// - Cannot decode group element field. + /// + /// # Panics #[allow(clippy::unwrap_used)] pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result { Ok(Self( @@ -75,18 +40,6 @@ mod tests { use super::*; - #[proptest] - fn keys_to_bytes_from_bytes_test(s1: SecretKey) { - let bytes = s1.to_bytes(); - let s2 = SecretKey::from_bytes(bytes).unwrap(); - assert_eq!(s1, s2); - - let p1 = s1.public_key(); - let bytes = p1.to_bytes(); - let p2 = PublicKey::from_bytes(&bytes).unwrap(); - assert_eq!(p1, p2); - } - #[proptest] fn ciphertext_to_bytes_from_bytes_test(c1: Ciphertext) { let bytes = c1.to_bytes(); diff --git a/rust/catalyst-voting/src/crypto/elgamal/mod.rs b/rust/catalyst-voting/src/crypto/elgamal/mod.rs index aefed68f660..25b1920284c 100644 --- a/rust/catalyst-voting/src/crypto/elgamal/mod.rs +++ b/rust/catalyst-voting/src/crypto/elgamal/mod.rs @@ -1,55 +1,17 @@ -//! Implementation of the lifted ``ElGamal`` crypto system, and combine with `ChaCha` +//! Implementation of the lifted `ElGamal` crypto system, and combine with `ChaCha` //! stream cipher to produce a hybrid encryption scheme. mod decoding; -use std::ops::{Add, Deref, Mul}; - -use rand_core::CryptoRngCore; +use std::ops::{Add, Mul}; use crate::crypto::group::{GroupElement, Scalar}; -/// ``ElGamal`` secret key. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct SecretKey(Scalar); - -/// ``ElGamal`` public key. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct PublicKey(GroupElement); - -/// ``ElGamal`` ciphertext, encrypted message with the public key. +/// `ElGamal` ciphertext, encrypted message with the public key. #[derive(Debug, Clone, PartialEq, Eq)] +#[must_use] pub struct Ciphertext(GroupElement, GroupElement); -impl Deref for SecretKey { - type Target = Scalar; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Deref for PublicKey { - type Target = GroupElement; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl SecretKey { - /// Generate a random `SecretKey` value from the random number generator. - pub fn random(rng: &mut R) -> Self { - Self(Scalar::random(rng)) - } - - /// Generate a corresponding `PublicKey`. - #[must_use] - pub fn public_key(&self) -> PublicKey { - PublicKey(GroupElement::GENERATOR.mul(&self.0)) - } -} - impl Ciphertext { /// Generate a zero `Ciphertext`. /// The same as encrypt a `Scalar::zero()` message and `Scalar::zero()` randomness. @@ -68,19 +30,24 @@ impl Ciphertext { } } +/// Generate `ElGamal` public key from the secret key value. +pub fn generate_public_key(secret_key: &Scalar) -> GroupElement { + GroupElement::GENERATOR.mul(secret_key) +} + /// Given a `message` represented as a `Scalar`, return a ciphertext using the -/// lifted ``ElGamal`` mechanism. +/// lifted `ElGamal` mechanism. /// Returns a ciphertext of type `Ciphertext`. -pub fn encrypt(message: &Scalar, public_key: &PublicKey, randomness: &Scalar) -> Ciphertext { +pub fn encrypt(message: &Scalar, public_key: &GroupElement, randomness: &Scalar) -> Ciphertext { let e1 = GroupElement::GENERATOR.mul(randomness); - let e2 = &GroupElement::GENERATOR.mul(message) + &public_key.0.mul(randomness); + let e2 = &GroupElement::GENERATOR.mul(message) + &public_key.mul(randomness); Ciphertext(e1, e2) } -/// Decrypt ``ElGamal`` `Ciphertext`, returns the original message represented as a +/// Decrypt `ElGamal` `Ciphertext`, returns the original message represented as a /// `GroupElement`. -pub fn decrypt(cipher: &Ciphertext, secret_key: &SecretKey) -> GroupElement { - &(&cipher.0 * &secret_key.0.negate()) + &cipher.1 +pub fn decrypt(cipher: &Ciphertext, secret_key: &Scalar) -> GroupElement { + &(&cipher.0 * &secret_key.negate()) + &cipher.1 } impl Mul<&Scalar> for &Ciphertext { @@ -109,15 +76,6 @@ mod tests { use super::*; - impl Arbitrary for SecretKey { - type Parameters = (); - type Strategy = BoxedStrategy; - - fn arbitrary_with((): Self::Parameters) -> Self::Strategy { - any::().prop_map(SecretKey).boxed() - } - } - impl Arbitrary for Ciphertext { type Parameters = (); type Strategy = BoxedStrategy; @@ -152,10 +110,8 @@ mod tests { } #[proptest] - fn elgamal_encryption_decryption_test( - secret_key: SecretKey, message: Scalar, randomness: Scalar, - ) { - let public_key = secret_key.public_key(); + fn elgamal_encryption_decryption_test(secret_key: Scalar, message: Scalar, randomness: Scalar) { + let public_key = generate_public_key(&secret_key); let cipher = encrypt(&message, &public_key, &randomness); let decrypted = decrypt(&cipher, &secret_key); diff --git a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs index b636f8a8648..d291da41828 100644 --- a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs +++ b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs @@ -11,20 +11,23 @@ use std::{ use curve25519_dalek::{ constants::{RISTRETTO_BASEPOINT_POINT, RISTRETTO_BASEPOINT_TABLE}, - digest::{consts::U64, Digest}, - ristretto::RistrettoPoint as Point, scalar::Scalar as IScalar, traits::Identity, + RistrettoPoint, }; use rand_core::CryptoRngCore; +use crate::crypto::hash::digest::{consts::U64, Digest}; + /// Ristretto group scalar. #[derive(Debug, Clone, PartialEq, Eq)] +#[must_use] pub struct Scalar(IScalar); /// Ristretto group element. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct GroupElement(Point); +#[must_use] +pub struct GroupElement(RistrettoPoint); impl From for Scalar { fn from(value: u64) -> Self { @@ -41,9 +44,7 @@ impl Hash for GroupElement { impl Scalar { /// Generate a random scalar value from the random number generator. pub fn random(rng: &mut R) -> Self { - let mut scalar_bytes = [0u8; 64]; - rng.fill_bytes(&mut scalar_bytes); - Scalar(IScalar::from_bytes_mod_order_wide(&scalar_bytes)) + Scalar(IScalar::random(rng)) } /// additive identity @@ -84,7 +85,13 @@ impl GroupElement { /// Generate a zero group element. pub fn zero() -> Self { - GroupElement(Point::identity()) + GroupElement(RistrettoPoint::identity()) + } + + /// Generate a `GroupElement` from a hash digest. + pub fn from_hash(hash: D) -> GroupElement + where D: Digest + Default { + GroupElement(RistrettoPoint::from_hash(hash)) } } diff --git a/rust/catalyst-voting/src/crypto/hash.rs b/rust/catalyst-voting/src/crypto/hash.rs index 93a6aff3215..7ce3fe6f0b1 100644 --- a/rust/catalyst-voting/src/crypto/hash.rs +++ b/rust/catalyst-voting/src/crypto/hash.rs @@ -1,11 +1,15 @@ -//! Blake2b-256 hash implementation. +//! Different hash implementations. -use curve25519_dalek::digest::{ - consts::U64, typenum::Unsigned, FixedOutput, HashMarker, Output, OutputSizeUser, Update, +pub use curve25519_dalek::digest; +use digest::{ + consts::{U32, U64}, + typenum::Unsigned, + FixedOutput, HashMarker, Output, OutputSizeUser, Update, }; /// Blake2b-512 hasher instance. #[derive(Clone, Debug)] +#[must_use] pub struct Blake2b512Hasher(blake2b_simd::State); impl Blake2b512Hasher { @@ -49,3 +53,50 @@ impl FixedOutput for Blake2b512Hasher { } impl HashMarker for Blake2b512Hasher {} + +/// Blake2b-256 hasher instance. +#[derive(Clone, Debug)] +#[must_use] +pub struct Blake2b256Hasher(blake2b_simd::State); + +impl Blake2b256Hasher { + /// Create a new `Blake2b256Hasher`. + pub fn new() -> Self { + Self( + blake2b_simd::Params::new() + .hash_length(Self::output_size()) + .to_state(), + ) + } +} + +// Implementation of the `digest::Digest` trait for `Blake2b256Hasher`. + +impl Default for Blake2b256Hasher { + fn default() -> Self { + Self::new() + } +} + +impl Update for Blake2b256Hasher { + fn update(&mut self, data: &[u8]) { + self.0.update(data); + } +} + +impl OutputSizeUser for Blake2b256Hasher { + type OutputSize = U32; + + fn output_size() -> usize { + Self::OutputSize::USIZE + } +} + +impl FixedOutput for Blake2b256Hasher { + fn finalize_into(self, out: &mut Output) { + let hash = self.0.finalize(); + out.copy_from_slice(hash.as_bytes()); + } +} + +impl HashMarker for Blake2b256Hasher {} diff --git a/rust/catalyst-voting/src/crypto/mod.rs b/rust/catalyst-voting/src/crypto/mod.rs index 32efae43415..0be3852f7bb 100644 --- a/rust/catalyst-voting/src/crypto/mod.rs +++ b/rust/catalyst-voting/src/crypto/mod.rs @@ -1,8 +1,20 @@ //! Crypto primitives which are used by voting protocol. +// cspell: words Seedable + +use rand_chacha::ChaCha8Rng; +use rand_core::{CryptoRngCore, SeedableRng}; + pub mod babystep_giantstep; +pub mod ed25519; pub mod elgamal; pub mod group; pub mod hash; pub mod zk_dl_equality; pub mod zk_unit_vector; + +/// Default random number generator `rand_chacha::ChaCha8Rng`. +#[must_use] +pub fn default_rng() -> impl CryptoRngCore { + ChaCha8Rng::from_entropy() +} diff --git a/rust/catalyst-voting/src/crypto/zk_dl_equality.rs b/rust/catalyst-voting/src/crypto/zk_dl_equality.rs index 8b17aa55851..b3884458147 100644 --- a/rust/catalyst-voting/src/crypto/zk_dl_equality.rs +++ b/rust/catalyst-voting/src/crypto/zk_dl_equality.rs @@ -12,14 +12,13 @@ // cspell: words NIZK dlog -use curve25519_dalek::digest::Digest; - use crate::crypto::{ group::{GroupElement, Scalar}, - hash::Blake2b512Hasher, + hash::{digest::Digest, Blake2b512Hasher}, }; /// DLEQ proof struct +#[must_use] pub struct DleqProof(Scalar, Scalar); /// Generates a DLEQ proof. @@ -37,6 +36,7 @@ pub fn generate_dleq_proof( } /// Verify a DLEQ proof. +#[must_use] pub fn verify_dleq_proof( proof: &DleqProof, base_1: &GroupElement, base_2: &GroupElement, point_1: &GroupElement, point_2: &GroupElement, diff --git a/rust/catalyst-voting/src/crypto/zk_unit_vector/challenges.rs b/rust/catalyst-voting/src/crypto/zk_unit_vector/challenges.rs index 5033e8b2e43..35632a68f0c 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/challenges.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/challenges.rs @@ -1,18 +1,15 @@ //! ZK unit vector challenges calculation functionality -use curve25519_dalek::digest::Digest; - -use crate::{ - crypto::{ - elgamal::Ciphertext, group::GroupElement, hash::Blake2b512Hasher, - zk_unit_vector::randomness_announcements::Announcement, - }, - PublicKey, +use crate::crypto::{ + elgamal::Ciphertext, + group::GroupElement, + hash::{digest::Digest, Blake2b512Hasher}, + zk_unit_vector::randomness_announcements::Announcement, }; /// Calculates the first challenge hash. pub(crate) fn calculate_first_challenge_hash( - commitment_key: &GroupElement, public_key: &PublicKey, ciphertexts: &[Ciphertext], + commitment_key: &GroupElement, public_key: &GroupElement, ciphertexts: &[Ciphertext], announcements: &[Announcement], ) -> Blake2b512Hasher { let mut hash = Blake2b512Hasher::new() diff --git a/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs b/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs index 82b1229fb3c..a6faaf50a50 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs @@ -11,6 +11,7 @@ impl UnitVectorProof { /// Get an underlying vector length. /// /// **Note** each vector field has the same length. + #[must_use] pub fn size(&self) -> usize { self.0.len() } diff --git a/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs b/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs index 1f2c21bb42d..1e85ad2dd79 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs @@ -21,12 +21,13 @@ use randomness_announcements::{Announcement, BlindingRandomness, ResponseRandomn use utils::get_bit; use crate::crypto::{ - elgamal::{encrypt, Ciphertext, PublicKey}, + elgamal::{encrypt, Ciphertext}, group::{GroupElement, Scalar}, }; /// Unit vector proof struct #[derive(Debug, Clone, PartialEq, Eq)] +#[must_use] pub struct UnitVectorProof( Vec, Vec, @@ -45,8 +46,8 @@ pub struct UnitVectorProof( /// the proof will be invalid. pub fn generate_unit_vector_proof( unit_vector: &[Scalar], mut ciphertexts: Vec, - mut encryption_randomness: Vec, public_key: &PublicKey, commitment_key: &GroupElement, - rng: &mut R, + mut encryption_randomness: Vec, public_key: &GroupElement, + commitment_key: &GroupElement, rng: &mut R, ) -> UnitVectorProof { let i = unit_vector .iter() @@ -104,7 +105,7 @@ pub fn generate_unit_vector_proof( /// Generates `D_l` and `R_l` elements #[allow(clippy::indexing_slicing)] fn generate_dl_and_rl( - log_n: u32, ch_1: &Scalar, public_key: &PublicKey, polynomials: &[Polynomial], rng: &mut R, + log_n: u32, ch_1: &Scalar, public_key: &GroupElement, polynomials: &[Polynomial], rng: &mut R, ) -> (Vec, Vec) { let r_l: Vec<_> = (0..log_n).map(|_| Scalar::random(rng)).collect(); @@ -157,8 +158,9 @@ fn generate_response( } /// Verify a unit vector proof. +#[must_use] pub fn verify_unit_vector_proof( - proof: &UnitVectorProof, mut ciphertexts: Vec, public_key: &PublicKey, + proof: &UnitVectorProof, mut ciphertexts: Vec, public_key: &GroupElement, commitment_key: &GroupElement, ) -> bool { let m = ciphertexts.len(); @@ -197,7 +199,7 @@ fn check_1(proof: &UnitVectorProof, ch_2: &Scalar, commitment_key: &GroupElement /// Check the second part of the proof fn check_2( proof: &UnitVectorProof, log_n: u32, ch_1: &Scalar, ch_2: &Scalar, ciphertexts: &[Ciphertext], - public_key: &PublicKey, + public_key: &GroupElement, ) -> bool { let left = encrypt(&Scalar::zero(), public_key, &proof.3); @@ -241,7 +243,7 @@ mod tests { use rand_core::OsRng; use test_strategy::proptest; - use super::{super::elgamal::SecretKey, *}; + use super::{super::elgamal::generate_public_key, *}; impl Arbitrary for UnitVectorProof { type Parameters = usize; @@ -269,13 +271,13 @@ mod tests { #[proptest] fn zk_unit_vector_test( - secret_key: SecretKey, commitment_key: GroupElement, + secret_key: Scalar, commitment_key: GroupElement, #[strategy(1..10_usize)] unit_vector_size: usize, #[strategy(0..#unit_vector_size)] unit_vector_index: usize, ) { let mut rng = OsRng; - let public_key = secret_key.public_key(); + let public_key = generate_public_key(&secret_key); let unit_vector: Vec<_> = (0..unit_vector_size) .map(|i| { @@ -319,7 +321,7 @@ mod tests { #[proptest] fn not_a_unit_vector_test( - secret_key: SecretKey, commitment_key: GroupElement, + secret_key: Scalar, commitment_key: GroupElement, #[any(size_range(1..10_usize).lift())] random_vector: Vec, ) { let mut rng = OsRng; @@ -330,7 +332,7 @@ mod tests { return Ok(()); } - let public_key = secret_key.public_key(); + let public_key = generate_public_key(&secret_key); let encryption_randomness: Vec<_> = random_vector .iter() diff --git a/rust/catalyst-voting/src/lib.rs b/rust/catalyst-voting/src/lib.rs index b494392750a..e0d911e7180 100644 --- a/rust/catalyst-voting/src/lib.rs +++ b/rust/catalyst-voting/src/lib.rs @@ -1,8 +1,6 @@ //! Voting primitives which are used among Catalyst ecosystem. -mod crypto; +pub mod crypto; pub mod txs; mod utils; pub mod vote_protocol; - -pub use crypto::elgamal::{PublicKey, SecretKey}; diff --git a/rust/catalyst-voting/src/txs/v1/decoding.rs b/rust/catalyst-voting/src/txs/v1/decoding.rs index 25628302253..b6fe7ef5943 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -4,8 +4,11 @@ use std::io::Read; use anyhow::{anyhow, bail, ensure}; -use super::{EncryptedVote, PublicKey, Tx, Vote, VoterProof}; -use crate::utils::{read_array, read_be_u32, read_be_u64, read_be_u8}; +use super::{EncryptedVote, Tx, VotePayload, VoterProof}; +use crate::{ + crypto::ed25519::{PublicKey, Signature}, + utils::{read_array, read_be_u32, read_be_u64, read_be_u8}, +}; /// Jörmungandr tx fragment tag. const FRAGMENT_TAG: u8 = 11; @@ -21,51 +24,74 @@ const PADDING_TAG: u8 = 0; const PRIVATE_VOTE_TAG: u8 = 2; /// Jörmungandr tx public vote tag. const PUBLIC_VOTE_TAG: u8 = 1; +/// Jörmungandr tx witness tag. +const WITNESS_TAG: u8 = 2; impl Tx { - /// Convert this `Tx` to its underlying sequence of bytes. - #[must_use] + /// Write the bytes of the `Tx` body to provided `buf`. #[allow(clippy::cast_possible_truncation)] - pub fn to_bytes(&self) -> Vec { - // Initialize already with the padding tag `0` and fragment tag `11`. - let mut tx_body = vec![PADDING_TAG, FRAGMENT_TAG]; + pub(super) fn tx_body_decode( + vote_plan_id: &[u8; 32], proposal_index: u8, vote: &VotePayload, public_key: &PublicKey, + buf: &mut Vec, + ) { + buf.extend_from_slice(vote_plan_id); + buf.push(proposal_index); - tx_body.extend_from_slice(&self.vote_plan_id); - tx_body.push(self.proposal_index); - - match &self.vote { - Vote::Public(vote) => { + match vote { + VotePayload::Public(vote) => { // Public vote tag - tx_body.push(PUBLIC_VOTE_TAG); - tx_body.push(*vote); + buf.push(PUBLIC_VOTE_TAG); + buf.push(*vote); }, - Vote::Private(vote, proof) => { + VotePayload::Private(vote, proof) => { // Private vote tag - tx_body.push(PRIVATE_VOTE_TAG); - tx_body.push(vote.size() as u8); - tx_body.extend_from_slice(&vote.to_bytes()); + buf.push(PRIVATE_VOTE_TAG); + buf.push(vote.size() as u8); + buf.extend_from_slice(&vote.to_bytes()); - tx_body.push(proof.size() as u8); - tx_body.extend_from_slice(&proof.to_bytes()); + buf.push(proof.size() as u8); + buf.extend_from_slice(&proof.to_bytes()); }, } // Zeros block date - tx_body.extend_from_slice(&[0u8; 8]); + buf.extend_from_slice(&[0u8; 8]); // Number of inputs - tx_body.push(NUMBER_OF_INPUTS); + buf.push(NUMBER_OF_INPUTS); // Number of outputs - tx_body.push(NUMBER_OF_OUTPUTS); + buf.push(NUMBER_OF_OUTPUTS); // Input tag - tx_body.push(INPUT_TAG); + buf.push(INPUT_TAG); // Zero value - tx_body.extend_from_slice(&[0u8; 8]); + buf.extend_from_slice(&[0u8; 8]); - tx_body.extend_from_slice(&self.public_key.to_bytes()); + buf.extend_from_slice(&public_key.to_bytes()); + } + + /// Convert this `Tx` to its underlying sequence of bytes. + #[must_use] + #[allow(clippy::cast_possible_truncation)] + pub fn to_bytes(&self) -> Vec { + // Initialize already with the padding tag `0` and fragment tag `11`. + let mut buf = vec![PADDING_TAG, FRAGMENT_TAG]; + + Self::tx_body_decode( + &self.vote_plan_id, + self.proposal_index, + &self.vote, + &self.public_key, + &mut buf, + ); + + // Witness tag + buf.push(WITNESS_TAG); + // Zero nonce + buf.extend_from_slice(&[0u8; 4]); + buf.extend_from_slice(&self.signature.to_bytes()); // Add the size of decoded bytes to the beginning. - let mut res = (tx_body.len() as u32).to_be_bytes().to_vec(); - res.append(&mut tx_body); + let mut res = (buf.len() as u32).to_be_bytes().to_vec(); + res.append(&mut buf); res } @@ -81,40 +107,44 @@ impl Tx { #[allow(clippy::indexing_slicing)] pub fn from_bytes(reader: &mut R) -> anyhow::Result { // Skip tx size field - read_be_u32(reader)?; + read_be_u32(reader).map_err(|_| anyhow!("Missing tx size field."))?; - let padding_tag = read_be_u8(reader)?; + let padding_tag = read_be_u8(reader).map_err(|_| anyhow!("Missing padding tag field."))?; ensure!( padding_tag == PADDING_TAG, "Invalid padding tag field value, must be equals to {PADDING_TAG}, provided: {padding_tag}.", ); - let fragment_tag = read_be_u8(reader)?; + let fragment_tag = + read_be_u8(reader).map_err(|_| anyhow!("Missing fragment tag field."))?; ensure!( fragment_tag == FRAGMENT_TAG, "Invalid fragment tag field value, must be equals to {FRAGMENT_TAG}, provided: {fragment_tag}.", ); - let vote_plan_id = read_array(reader)?; + let vote_plan_id = + read_array(reader).map_err(|_| anyhow!("Missing vote plan id field."))?; - let proposal_index = read_be_u8(reader)?; + let proposal_index = + read_be_u8(reader).map_err(|_| anyhow!("Missing proposal index field."))?; - let vote_tag = read_be_u8(reader)?; + let vote_tag = read_be_u8(reader).map_err(|_| anyhow!("Missing vote tag field."))?; let vote = match vote_tag { PUBLIC_VOTE_TAG => { - let vote = read_be_u8(reader)?; - Vote::Public(vote) + let vote = + read_be_u8(reader).map_err(|_| anyhow!("Missing public vote choice field."))?; + VotePayload::Public(vote) }, PRIVATE_VOTE_TAG => { - let size = read_be_u8(reader)?; + let size = read_be_u8(reader).map_err(|_| anyhow!("Missing vote size field."))?; let vote = EncryptedVote::from_bytes(reader, size.into()) .map_err(|e| anyhow!("Invalid encrypted vote, error: {e}."))?; - let size = read_be_u8(reader)?; + let size = read_be_u8(reader).map_err(|_| anyhow!("Missing proof size field."))?; let proof = VoterProof::from_bytes(reader, size.into()) .map_err(|e| anyhow!("Invalid voter proof, error: {e}."))?; - Vote::Private(vote, proof) + VotePayload::Private(vote, proof) }, tag => { bail!( @@ -124,57 +154,181 @@ impl Tx { }; // skip block date (epoch and slot) - read_be_u64(reader)?; + read_be_u64(reader).map_err(|_| anyhow!("Missing block date field."))?; - let inputs_amount = read_be_u8(reader)?; + let inputs_amount = + read_be_u8(reader).map_err(|_| anyhow!("Missing inputs amount field."))?; ensure!( inputs_amount == NUMBER_OF_INPUTS, "Invalid number of inputs, expected: {NUMBER_OF_INPUTS}, provided: {inputs_amount}", ); - let outputs_amount = read_be_u8(reader)?; + let outputs_amount = + read_be_u8(reader).map_err(|_| anyhow!("Missing outputs amount field."))?; ensure!( outputs_amount == NUMBER_OF_OUTPUTS, "Invalid number of outputs, expected: {NUMBER_OF_OUTPUTS}, provided: {outputs_amount}", ); - let input_tag = read_be_u8(reader)?; + let input_tag = read_be_u8(reader).map_err(|_| anyhow!("Missing input tag field."))?; ensure!( input_tag == INPUT_TAG, "Invalid input tag, expected: {INPUT_TAG}, provided: {input_tag}", ); // skip value - read_be_u64(reader)?; + read_be_u64(reader).map_err(|_| anyhow!("Missing value field."))?; - let public_key_bytes = read_array(reader)?; + let public_key_bytes = + read_array(reader).map_err(|_| anyhow!("Missing public_key field."))?; let public_key = PublicKey::from_bytes(&public_key_bytes) .map_err(|e| anyhow!("Invalid public key, error: {e}."))?; + let witness_tag = read_be_u8(reader).map_err(|_| anyhow!("Missing witness tag field."))?; + ensure!( + witness_tag == WITNESS_TAG, + "Invalid witness tag, expected: {WITNESS_TAG}, provided: {witness_tag}", + ); + + // Skip nonce field + read_be_u32(reader).map_err(|_| anyhow!("Missing nonce field."))?; + + let signature_bytes = + read_array(reader).map_err(|_| anyhow!("Missing signature field."))?; + let signature = Signature::from_bytes(&signature_bytes); + Ok(Self { vote_plan_id, proposal_index, vote, public_key, + signature, }) } } #[cfg(test)] mod tests { + use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, Strategy}; use test_strategy::proptest; use super::*; + use crate::crypto::ed25519::PrivateKey; + + impl Arbitrary for Tx { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + any::<( + [u8; 32], + u8, + VotePayload, + PrivateKey, + [u8; Signature::BYTES_SIZE], + )>() + .prop_map( + |(vote_plan_id, proposal_index, vote, sk, signature_bytes)| { + Tx { + vote_plan_id, + proposal_index, + vote, + public_key: sk.public_key(), + signature: Signature::from_bytes(&signature_bytes), + } + }, + ) + .boxed() + } + } + + impl Arbitrary for VotePayload { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + any::() + .prop_flat_map(|b| { + if b { + any::().prop_map(VotePayload::Public).boxed() + } else { + any::<(u8, u8)>() + .prop_flat_map(|(s1, s2)| { + any_with::<(EncryptedVote, VoterProof)>((s1.into(), s2.into())) + .prop_map(|(v, p)| VotePayload::Private(v, p)) + }) + .boxed() + } + }) + .boxed() + } + } #[proptest] - #[allow(clippy::indexing_slicing)] fn tx_to_bytes_from_bytes_test(t1: Tx) { let bytes = t1.to_bytes(); - // verify correctness serializing tx size field - let size = u32::from_be_bytes(bytes[0..4].try_into().unwrap()); + let mut reader = bytes.as_slice(); + + let size = read_be_u32(&mut reader).unwrap(); assert_eq!(size as usize, bytes.len() - 4); + let padding_tag = read_be_u8(&mut reader).unwrap(); + assert_eq!(padding_tag, PADDING_TAG); + + let fragment_tag = read_be_u8(&mut reader).unwrap(); + assert_eq!(fragment_tag, FRAGMENT_TAG); + + let vote_plan_id = read_array(&mut reader).unwrap(); + assert_eq!(vote_plan_id, t1.vote_plan_id); + + let proposal_index = read_be_u8(&mut reader).unwrap(); + assert_eq!(proposal_index, t1.proposal_index); + + let vote_tag = read_be_u8(&mut reader).unwrap(); + assert!(vote_tag == PUBLIC_VOTE_TAG || vote_tag == PRIVATE_VOTE_TAG); + match vote_tag { + PUBLIC_VOTE_TAG => { + let vote = read_be_u8(&mut reader).unwrap(); + assert_eq!(VotePayload::Public(vote), t1.vote); + }, + PRIVATE_VOTE_TAG => { + let size = read_be_u8(&mut reader).unwrap(); + let vote = EncryptedVote::from_bytes(&mut reader, size.into()).unwrap(); + let size = read_be_u8(&mut reader).unwrap(); + let proof = VoterProof::from_bytes(&mut reader, size.into()).unwrap(); + assert_eq!(VotePayload::Private(vote, proof), t1.vote); + }, + _ => {}, + } + + let block_date = read_be_u64(&mut reader).unwrap(); + assert_eq!(block_date, 0); + + let inputs_amount = read_be_u8(&mut reader).unwrap(); + assert_eq!(inputs_amount, NUMBER_OF_INPUTS); + + let outputs_amount = read_be_u8(&mut reader).unwrap(); + assert_eq!(outputs_amount, NUMBER_OF_OUTPUTS); + + let input_tag = read_be_u8(&mut reader).unwrap(); + assert_eq!(input_tag, INPUT_TAG); + + let value = read_be_u64(&mut reader).unwrap(); + assert_eq!(value, 0); + + let public_key = read_array(&mut reader).unwrap(); + assert_eq!(PublicKey::from_bytes(&public_key).unwrap(), t1.public_key); + + let witness_tag = read_be_u8(&mut reader).unwrap(); + assert_eq!(witness_tag, WITNESS_TAG); + + let nonce = read_be_u32(&mut reader).unwrap(); + assert_eq!(nonce, 0); + + let signature = read_array(&mut reader).unwrap(); + assert_eq!(Signature::from_bytes(&signature), t1.signature); + let t2 = Tx::from_bytes(&mut bytes.as_slice()).unwrap(); assert_eq!(t1, t2); } diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index a78a1db10c0..fbc19d6e9e3 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -1,80 +1,340 @@ //! A Jörmungandr transaction object structured following this [spec](https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/voting_transaction/transaction/#v1-jormungandr) - -#![allow(unused_variables, dead_code)] +//! +//! ```rust +//! use catalyst_voting::{ +//! crypto::{default_rng, ed25519::PrivateKey}, +//! txs::v1::Tx, +//! vote_protocol::committee::ElectionSecretKey, +//! }; +//! +//! let vote_plan_id = [0u8; 32]; +//! let proposal_index = 0u8; +//! +//! let voting_options = 3; +//! let choice = 1; +//! +//! let users_private_key = PrivateKey::random(&mut default_rng()); +//! let election_public_key = ElectionSecretKey::random_with_default_rng().public_key(); +//! +//! let public_tx = Tx::new_public( +//! vote_plan_id, +//! proposal_index, +//! voting_options, +//! choice, +//! &users_private_key, +//! ) +//! .unwrap(); +//! public_tx.verify_signature().unwrap(); +//! +//! let private_tx = Tx::new_private_with_default_rng( +//! vote_plan_id, +//! proposal_index, +//! voting_options, +//! choice, +//! &election_public_key, +//! &users_private_key, +//! ) +//! .unwrap(); +//! private_tx.verify_signature().unwrap(); +//! private_tx.verify_proof(&election_public_key).unwrap(); +//! ``` mod decoding; +use anyhow::ensure; +use rand_core::CryptoRngCore; + use crate::{ - vote_protocol::voter::{proof::VoterProof, EncryptedVote}, - PublicKey, + crypto::{ + default_rng, + ed25519::{sign, verify_signature, PrivateKey, PublicKey, Signature}, + hash::{digest::Digest, Blake2b256Hasher, Blake2b512Hasher}, + }, + vote_protocol::{ + committee::{ElectionPublicKey, ElectionSecretKey}, + voter::{ + decrypt_vote, encrypt_vote_with_default_rng, + proof::{generate_voter_proof, verify_voter_proof, VoterProof, VoterProofCommitment}, + EncryptedVote, Vote, + }, + }, }; /// A v1 (Jörmungandr) transaction struct #[derive(Debug, Clone, PartialEq, Eq)] +#[must_use] pub struct Tx { /// Vote plan id vote_plan_id: [u8; 32], /// Proposal index proposal_index: u8, /// Vote - vote: Vote, + vote: VotePayload, /// Public key public_key: PublicKey, + /// Transaction signature + signature: Signature, } -/// Vote struct +/// Vote payload struct. +/// Contains all necessary information for the valid vote. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Vote { +pub enum VotePayload { /// Public voting choice Public(u8), /// Private (encrypted) voting choice Private(EncryptedVote, VoterProof), } -#[cfg(test)] -mod tests { - use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, Strategy}; +impl Tx { + /// Generate a new `Tx` with public vote. + /// + /// # Errors + /// - Invalid voting choice + pub fn new_public( + vote_plan_id: [u8; 32], proposal_index: u8, voting_options: u8, choice: u8, + users_private_key: &PrivateKey, + ) -> anyhow::Result { + let vote = VotePayload::new_public(choice, voting_options)?; + let signature = Self::sign(&vote_plan_id, proposal_index, &vote, users_private_key); + Ok(Self { + vote_plan_id, + proposal_index, + vote, + public_key: users_private_key.public_key(), + signature, + }) + } - use super::*; - use crate::SecretKey; - - impl Arbitrary for Tx { - type Parameters = (); - type Strategy = BoxedStrategy; - - fn arbitrary_with((): Self::Parameters) -> Self::Strategy { - any::<([u8; 32], u8, Vote, SecretKey)>() - .prop_map(|(vote_plan_id, proposal_index, vote, s)| { - Tx { - vote_plan_id, - proposal_index, - vote, - public_key: s.public_key(), - } - }) - .boxed() + /// Generate a new `Tx` with public vote. + /// + /// # Errors + /// - Invalid voting choice + pub fn new_private( + vote_plan_id: [u8; 32], proposal_index: u8, voting_options: u8, choice: u8, + election_public_key: &ElectionPublicKey, users_private_key: &PrivateKey, rng: &mut R, + ) -> anyhow::Result { + let vote = VotePayload::new_private( + &vote_plan_id, + choice, + voting_options, + election_public_key, + rng, + )?; + let signature = Self::sign(&vote_plan_id, proposal_index, &vote, users_private_key); + + Ok(Self { + vote_plan_id, + proposal_index, + vote, + public_key: users_private_key.public_key(), + signature, + }) + } + + /// Generate a new `Tx` with public vote with `crypto::default_rng`. + /// + /// # Errors + /// - Invalid voting choice + pub fn new_private_with_default_rng( + vote_plan_id: [u8; 32], proposal_index: u8, voting_options: u8, choice: u8, + election_public_key: &ElectionPublicKey, users_private_key: &PrivateKey, + ) -> anyhow::Result { + Self::new_private( + vote_plan_id, + proposal_index, + voting_options, + choice, + election_public_key, + users_private_key, + &mut default_rng(), + ) + } + + /// Returns `true` if the vote is public + #[must_use] + pub fn is_public(&self) -> bool { + matches!(self.vote, VotePayload::Public(_)) + } + + /// Returns `true` if the vote is private + #[must_use] + pub fn is_private(&self) -> bool { + matches!(self.vote, VotePayload::Private(_, _)) + } + + /// Returns public voting choice. + /// + /// # Errors + /// - Not a public vote + pub fn public_choice(&self) -> anyhow::Result { + if let VotePayload::Public(choice) = &self.vote { + Ok(*choice) + } else { + Err(anyhow::anyhow!("Not a public vote")) } } - impl Arbitrary for Vote { - type Parameters = (); - type Strategy = BoxedStrategy; - - fn arbitrary_with((): Self::Parameters) -> Self::Strategy { - any::() - .prop_flat_map(|b| { - if b { - any::().prop_map(Vote::Public).boxed() - } else { - any::<(u8, u8)>() - .prop_flat_map(|(s1, s2)| { - any_with::<(EncryptedVote, VoterProof)>((s1.into(), s2.into())) - .prop_map(|(v, p)| Vote::Private(v, p)) - }) - .boxed() - } - }) - .boxed() + /// Returns private voting choice. + /// + /// # Errors + /// - Not a private vote + #[allow(clippy::cast_possible_truncation)] + pub fn private_choice(&self, secret_key: &ElectionSecretKey) -> anyhow::Result { + if let VotePayload::Private(vote, _) = &self.vote { + let vote = decrypt_vote(vote, secret_key)?; + let choice = vote.choice() as u8; + Ok(choice) + } else { + Err(anyhow::anyhow!("Not a private vote")) } } + + /// Verify transaction signature + /// + /// # Errors + /// - Invalid signature + pub fn verify_signature(&self) -> anyhow::Result<()> { + let bytes = Self::bytes_to_sign( + &self.vote_plan_id, + self.proposal_index, + &self.vote, + &self.public_key, + ); + ensure!( + verify_signature(&self.public_key, &bytes, &self.signature), + "Invalid signature." + ); + Ok(()) + } + + /// Verify transaction proof of the private vote. + /// If vote is public it returns `Ok(())` + /// + /// # Errors + /// - Invalid proof + pub fn verify_proof(&self, election_public_key: &ElectionPublicKey) -> anyhow::Result<()> { + if let VotePayload::Private(encrypted_vote, proof) = &self.vote { + let vote_plan_id_hash = Blake2b512Hasher::new().chain_update(self.vote_plan_id); + let commitment = VoterProofCommitment::from_hash(vote_plan_id_hash); + ensure!( + verify_voter_proof( + encrypted_vote.clone(), + election_public_key, + &commitment, + proof, + ), + "Invalid proof." + ); + } + Ok(()) + } + + /// Generate transaction signature + fn sign( + vote_plan_id: &[u8; 32], proposal_index: u8, vote: &VotePayload, private_key: &PrivateKey, + ) -> Signature { + let bytes = Self::bytes_to_sign( + vote_plan_id, + proposal_index, + vote, + &private_key.public_key(), + ); + sign(private_key, &bytes) + } + + /// Generate bytes to be signed. + /// A Blake2b256 hash of the transaction body + fn bytes_to_sign( + vote_plan_id: &[u8; 32], proposal_index: u8, vote: &VotePayload, public_key: &PublicKey, + ) -> Vec { + let mut bytes = Vec::new(); + Self::tx_body_decode(vote_plan_id, proposal_index, vote, public_key, &mut bytes); + Blake2b256Hasher::new() + .chain_update(bytes.as_slice()) + .finalize() + .to_vec() + } +} + +#[allow(clippy::missing_docs_in_private_items)] +impl VotePayload { + fn new_public(choice: u8, proposal_voting_options: u8) -> anyhow::Result { + // Try to make a `Vote` just for applying underlying validation, which must be the same + // even for public vote + Vote::new(choice.into(), proposal_voting_options.into())?; + Ok(Self::Public(choice)) + } + + fn new_private( + vote_plan_id: &[u8; 32], choice: u8, proposal_voting_options: u8, + election_public_key: &ElectionPublicKey, rng: &mut R, + ) -> anyhow::Result { + let vote = Vote::new(choice.into(), proposal_voting_options.into())?; + + let (encrypted_vote, randomness) = + encrypt_vote_with_default_rng(&vote, election_public_key); + + let vote_plan_id_hash = Blake2b512Hasher::new().chain_update(vote_plan_id); + let commitment = VoterProofCommitment::from_hash(vote_plan_id_hash); + + let voter_proof = generate_voter_proof( + &vote, + encrypted_vote.clone(), + randomness, + election_public_key, + &commitment, + rng, + )?; + + Ok(Self::Private(encrypted_vote, voter_proof)) + } +} + +#[cfg(test)] +mod tests { + use test_strategy::proptest; + + use super::*; + use crate::{crypto::ed25519::PrivateKey, vote_protocol::committee::ElectionSecretKey}; + + #[proptest] + fn tx_test( + vote_plan_id: [u8; 32], proposal_index: u8, #[strategy(1u8..5)] voting_options: u8, + #[strategy(0..#voting_options)] choice: u8, users_private_key: PrivateKey, + election_secret_key: ElectionSecretKey, + ) { + let election_public_key = election_secret_key.public_key(); + + let tx = Tx::new_public( + vote_plan_id, + proposal_index, + voting_options, + choice, + &users_private_key, + ) + .unwrap(); + assert!(tx.is_public()); + assert!(!tx.is_private()); + tx.verify_signature().unwrap(); + tx.verify_proof(&election_public_key).unwrap(); + assert_eq!(tx.public_choice().unwrap(), choice); + assert!(tx.private_choice(&election_secret_key).is_err()); + + let tx = Tx::new_private_with_default_rng( + vote_plan_id, + proposal_index, + voting_options, + choice, + &election_public_key, + &users_private_key, + ) + .unwrap(); + assert!(!tx.is_public()); + assert!(tx.is_private()); + tx.verify_signature().unwrap(); + tx.verify_proof(&election_public_key).unwrap(); + assert_eq!(tx.private_choice(&election_secret_key).unwrap(), choice); + assert!(tx.public_choice().is_err()); + } } diff --git a/rust/catalyst-voting/src/vote_protocol/committee.rs b/rust/catalyst-voting/src/vote_protocol/committee.rs new file mode 100644 index 00000000000..551dfe594f8 --- /dev/null +++ b/rust/catalyst-voting/src/vote_protocol/committee.rs @@ -0,0 +1,53 @@ +//! Module containing all primitives related to the committee. + +use rand_core::CryptoRngCore; + +use crate::crypto::{ + default_rng, + elgamal::generate_public_key, + group::{GroupElement, Scalar}, +}; + +/// Election secret key. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ElectionSecretKey(pub(crate) Scalar); + +impl ElectionSecretKey { + /// Randomly generate the `ElectionSecretKey`. + #[must_use] + pub fn random(rng: &mut R) -> Self { + Self(Scalar::random(rng)) + } + + /// Randomly generate the `ElectionSecretKey` with the `crypto::default_rng`. + #[must_use] + pub fn random_with_default_rng() -> Self { + Self::random(&mut default_rng()) + } + + /// Generate a corresponding `PublicKey`. + #[must_use] + pub fn public_key(&self) -> ElectionPublicKey { + ElectionPublicKey(generate_public_key(&self.0)) + } +} + +/// Election public key. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ElectionPublicKey(pub(crate) GroupElement); + +#[cfg(test)] +mod tests { + use proptest::prelude::{any, Arbitrary, BoxedStrategy, Strategy}; + + use super::*; + + impl Arbitrary for ElectionSecretKey { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + any::().prop_map(Self).boxed() + } + } +} diff --git a/rust/catalyst-voting/src/vote_protocol/mod.rs b/rust/catalyst-voting/src/vote_protocol/mod.rs index 16fb14d7bc3..7c2ab504a32 100644 --- a/rust/catalyst-voting/src/vote_protocol/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/mod.rs @@ -1,16 +1,20 @@ //! An implementation of the voting protocol described in this [spec](https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/voting_transaction/crypto/) //! //! ```rust -//! use catalyst_voting::{ -//! vote_protocol::{ -//! tally::{ -//! decrypt_tally, -//! proof::{generate_tally_proof, verify_tally_proof}, -//! tally, DecryptionTallySetup, +//! use catalyst_voting::vote_protocol::{ +//! committee::ElectionSecretKey, +//! tally::{ +//! decrypt_tally, +//! proof::{generate_tally_proof_with_default_rng, verify_tally_proof}, +//! tally, DecryptionTallySetup, +//! }, +//! voter::{ +//! encrypt_vote_with_default_rng, +//! proof::{ +//! generate_voter_proof_with_default_rng, verify_voter_proof, VoterProofCommitment, //! }, -//! voter::{encrypt_vote, Vote}, +//! Vote, //! }, -//! SecretKey, //! }; //! //! struct Voter { @@ -18,10 +22,11 @@ //! choice: usize, //! } //! -//! let mut rng = rand_core::OsRng; +//! // Initial setup //! let voting_options = 3; -//! let election_secret_key = SecretKey::random(&mut rng); +//! let election_secret_key = ElectionSecretKey::random_with_default_rng(); //! let election_public_key = election_secret_key.public_key(); +//! let voter_proof_commitment = VoterProofCommitment::random_with_default_rng(); //! //! let voter_1 = Voter { //! voting_power: 10, @@ -38,32 +43,75 @@ //! choice: 2, //! }; //! +//! // Generating votes //! let vote_1 = Vote::new(voter_1.choice, voting_options).unwrap(); //! let vote_2 = Vote::new(voter_2.choice, voting_options).unwrap(); //! let vote_3 = Vote::new(voter_3.choice, voting_options).unwrap(); //! //! let (encrypted_vote_1, voter_randomness_1) = -//! encrypt_vote(&vote_1, &election_public_key, &mut rng); +//! encrypt_vote_with_default_rng(&vote_1, &election_public_key); //! let (encrypted_vote_2, voter_randomness_2) = -//! encrypt_vote(&vote_2, &election_public_key, &mut rng); +//! encrypt_vote_with_default_rng(&vote_2, &election_public_key); //! let (encrypted_vote_3, voter_randomness_3) = -//! encrypt_vote(&vote_3, &election_public_key, &mut rng); -//! let encrypted_votes = vec![encrypted_vote_1, encrypted_vote_2, encrypted_vote_3]; +//! encrypt_vote_with_default_rng(&vote_3, &election_public_key); //! -//! let encrypted_tallies: Vec<_> = (0..voting_options) -//! .map(|voting_option| { -//! tally(voting_option, &encrypted_votes, &[ -//! voter_1.voting_power, -//! voter_2.voting_power, -//! voter_3.voting_power, -//! ]) -//! .unwrap() -//! }) -//! .collect(); +//! // Verify encrypted votes +//! { +//! let voter_proof_1 = generate_voter_proof_with_default_rng( +//! &vote_1, +//! encrypted_vote_1.clone(), +//! voter_randomness_1, +//! &election_public_key, +//! &voter_proof_commitment, +//! ) +//! .unwrap(); +//! assert!(verify_voter_proof( +//! encrypted_vote_1.clone(), +//! &election_public_key, +//! &voter_proof_commitment, +//! &voter_proof_1 +//! )); //! -//! let tally_proofs: Vec<_> = encrypted_tallies -//! .iter() -//! .map(|t| generate_tally_proof(t, &election_secret_key, &mut rng)) +//! let voter_proof_2 = generate_voter_proof_with_default_rng( +//! &vote_2, +//! encrypted_vote_2.clone(), +//! voter_randomness_2, +//! &election_public_key, +//! &voter_proof_commitment, +//! ) +//! .unwrap(); +//! assert!(verify_voter_proof( +//! encrypted_vote_2.clone(), +//! &election_public_key, +//! &voter_proof_commitment, +//! &voter_proof_2 +//! )); +//! +//! let voter_proof_3 = generate_voter_proof_with_default_rng( +//! &vote_3, +//! encrypted_vote_3.clone(), +//! voter_randomness_3, +//! &election_public_key, +//! &voter_proof_commitment, +//! ) +//! .unwrap(); +//! assert!(verify_voter_proof( +//! encrypted_vote_3.clone(), +//! &election_public_key, +//! &voter_proof_commitment, +//! &voter_proof_3 +//! )); +//! } +//! +//! // Tally step +//! let encrypted_votes = [encrypted_vote_1, encrypted_vote_2, encrypted_vote_3]; +//! let voting_powers = [ +//! voter_1.voting_power, +//! voter_2.voting_power, +//! voter_3.voting_power, +//! ]; +//! let encrypted_tallies: Vec<_> = (0..voting_options) +//! .map(|voting_option| tally(voting_option, &encrypted_votes, &voting_powers).unwrap()) //! .collect(); //! //! let decryption_tally_setup = DecryptionTallySetup::new( @@ -75,12 +123,20 @@ //! .map(|t| decrypt_tally(t, &election_secret_key, &decryption_tally_setup).unwrap()) //! .collect(); //! -//! let is_ok = tally_proofs -//! .iter() -//! .zip(encrypted_tallies.iter()) -//! .zip(decrypted_tallies.iter()) -//! .all(|((p, enc_t), t)| verify_tally_proof(enc_t, *t, &election_public_key, p)); -//! assert!(is_ok); +//! // Verify tallies +//! { +//! let tally_proofs: Vec<_> = encrypted_tallies +//! .iter() +//! .map(|t| generate_tally_proof_with_default_rng(t, &election_secret_key)) +//! .collect(); +//! +//! let is_ok = tally_proofs +//! .iter() +//! .zip(encrypted_tallies.iter()) +//! .zip(decrypted_tallies.iter()) +//! .all(|((p, enc_t), t)| verify_tally_proof(enc_t, *t, &election_public_key, p)); +//! assert!(is_ok); +//! } //! //! assert_eq!(decrypted_tallies, vec![ //! voter_1.voting_power, @@ -89,5 +145,6 @@ //! ]); //! ``` +pub mod committee; pub mod tally; pub mod voter; diff --git a/rust/catalyst-voting/src/vote_protocol/tally/mod.rs b/rust/catalyst-voting/src/vote_protocol/tally/mod.rs index 7e29cc2caef..33ee9269d97 100644 --- a/rust/catalyst-voting/src/vote_protocol/tally/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/tally/mod.rs @@ -6,10 +6,11 @@ use std::ops::{Add, Mul}; use anyhow::{anyhow, ensure}; +use super::committee::ElectionSecretKey; use crate::{ crypto::{ babystep_giantstep::BabyStepGiantStep, - elgamal::{decrypt, Ciphertext, SecretKey}, + elgamal::{decrypt, Ciphertext}, group::Scalar, }, vote_protocol::voter::EncryptedVote, @@ -92,9 +93,9 @@ pub fn tally( /// tally result. #[allow(clippy::module_name_repetitions)] pub fn decrypt_tally( - tally_result: &EncryptedTally, secret_key: &SecretKey, setup: &DecryptionTallySetup, + tally_result: &EncryptedTally, secret_key: &ElectionSecretKey, setup: &DecryptionTallySetup, ) -> anyhow::Result { - let ge = decrypt(&tally_result.0, secret_key); + let ge = decrypt(&tally_result.0, &secret_key.0); let res = setup .discrete_log_setup diff --git a/rust/catalyst-voting/src/vote_protocol/tally/proof.rs b/rust/catalyst-voting/src/vote_protocol/tally/proof.rs index 8417bf9fa70..e043e5d1588 100644 --- a/rust/catalyst-voting/src/vote_protocol/tally/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/tally/proof.rs @@ -8,49 +8,61 @@ use rand_core::CryptoRngCore; use super::EncryptedTally; use crate::{ crypto::{ + default_rng, group::{GroupElement, Scalar}, zk_dl_equality::{generate_dleq_proof, verify_dleq_proof, DleqProof}, }, - PublicKey, SecretKey, + vote_protocol::committee::{ElectionPublicKey, ElectionSecretKey}, }; /// Tally proof struct. #[allow(clippy::module_name_repetitions)] +#[must_use] pub struct TallyProof(DleqProof); /// Generates a tally proof. /// More detailed described [here](https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/voting_transaction/crypto/#tally-proof) #[allow(clippy::module_name_repetitions)] pub fn generate_tally_proof( - encrypted_tally: &EncryptedTally, secret_key: &SecretKey, rng: &mut R, + encrypted_tally: &EncryptedTally, secret_key: &ElectionSecretKey, rng: &mut R, ) -> TallyProof { let randomness = Scalar::random(rng); let e1 = encrypted_tally.0.first(); - let d = e1.mul(secret_key); + let d = e1.mul(&secret_key.0); let proof = generate_dleq_proof( &GroupElement::GENERATOR, e1, - &secret_key.public_key(), + &secret_key.public_key().0, &d, - secret_key, + &secret_key.0, &randomness, ); TallyProof(proof) } +/// Generates a tally proof with `crypto::default_rng`. +/// More detailed described [here](https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/voting_transaction/crypto/#tally-proof) +#[allow(clippy::module_name_repetitions)] +pub fn generate_tally_proof_with_default_rng( + encrypted_tally: &EncryptedTally, secret_key: &ElectionSecretKey, +) -> TallyProof { + generate_tally_proof(encrypted_tally, secret_key, &mut default_rng()) +} + /// Verifies a tally proof. /// More detailed described [here](https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/voting_transaction/crypto/#tally-proof) #[must_use] #[allow(clippy::module_name_repetitions)] pub fn verify_tally_proof( - encrypted_tally: &EncryptedTally, tally: u64, public_key: &PublicKey, proof: &TallyProof, + encrypted_tally: &EncryptedTally, tally: u64, public_key: &ElectionPublicKey, + proof: &TallyProof, ) -> bool { let tally = Scalar::from(tally); let e1 = encrypted_tally.0.first(); let e2 = encrypted_tally.0.second(); let d = e2 - &GroupElement::GENERATOR.mul(&tally); - verify_dleq_proof(&proof.0, &GroupElement::GENERATOR, e1, public_key, &d) + verify_dleq_proof(&proof.0, &GroupElement::GENERATOR, e1, &public_key.0, &d) } diff --git a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index 788bcca2940..bb267a6253a 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -3,11 +3,14 @@ mod decoding; pub mod proof; -use anyhow::ensure; +use anyhow::{anyhow, bail, ensure}; use rand_core::CryptoRngCore; +use super::committee::{ElectionPublicKey, ElectionSecretKey}; use crate::crypto::{ - elgamal::{encrypt, Ciphertext, PublicKey}, + babystep_giantstep::BabyStepGiantStep, + default_rng, + elgamal::{decrypt, encrypt, Ciphertext}, group::Scalar, }; @@ -61,6 +64,12 @@ impl Vote { }) } + /// Get the voter's choice. + #[must_use] + pub fn choice(&self) -> usize { + self.choice + } + /// Transform the vote into the unit vector. fn to_unit_vector(&self) -> Vec { (0..self.voting_options) @@ -75,13 +84,14 @@ impl Vote { } } -/// Create a new encrypted vote from the given vote and public key. -/// More detailed described [here](https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/voting_transaction/crypto/#vote-encryption) +/// Create a new encrypted vote from the given vote and public key with with the +/// `crypto::default_rng`. More detailed described [here](https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/voting_transaction/crypto/#vote-encryption) /// /// # Errors /// - `EncryptedVoteError` +#[must_use] pub fn encrypt_vote( - vote: &Vote, public_key: &PublicKey, rng: &mut R, + vote: &Vote, public_key: &ElectionPublicKey, rng: &mut R, ) -> (EncryptedVote, EncryptionRandomness) { let randomness = EncryptionRandomness::random(rng, vote.voting_options); @@ -89,12 +99,48 @@ pub fn encrypt_vote( let ciphers = unit_vector .iter() .zip(randomness.0.iter()) - .map(|(m, r)| encrypt(m, public_key, r)) + .map(|(m, r)| encrypt(m, &public_key.0, r)) .collect(); (EncryptedVote(ciphers), randomness) } +/// Create a new encrypted vote from the given vote and public key. +/// More detailed described [here](https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/voting_transaction/crypto/#vote-encryption) +#[must_use] +pub fn encrypt_vote_with_default_rng( + vote: &Vote, public_key: &ElectionPublicKey, +) -> (EncryptedVote, EncryptionRandomness) { + encrypt_vote(vote, public_key, &mut default_rng()) +} + +/// Decrypt the encrypted vote. +/// **NOTE** make sure tha the provided `vote` is a valid one, by executing the +/// `verify_voter_proof` on the underlying voter proof. +/// If not valid encrypted vote is provided, unexpected results may occur. +/// +/// # Errors +/// - Invalid encrypted vote, not a valid unit vector. +pub fn decrypt_vote(vote: &EncryptedVote, secret_key: &ElectionSecretKey) -> anyhow::Result { + // Assuming that the provided encrypted vote is a correctly encoded unit vector, + // the maximum log value is `1`. + let setup = BabyStepGiantStep::new(1, None)?; + + for (i, encrypted_choice_per_option) in vote.0.iter().enumerate() { + let decrypted_choice_per_option = decrypt(encrypted_choice_per_option, &secret_key.0); + let choice_per_option = setup + .discrete_log(decrypted_choice_per_option) + .map_err(|_| anyhow!("Invalid encrypted vote, not a valid unit vector."))?; + if choice_per_option == 1 { + return Ok(Vote { + choice: i, + voting_options: vote.0.len(), + }); + } + } + bail!("Invalid encrypted vote, not a valid unit vector.") +} + #[cfg(test)] mod tests { use proptest::{ @@ -120,6 +166,7 @@ mod tests { let voting_options = 3; let vote = Vote::new(0, voting_options).unwrap(); + assert_eq!(vote.choice(), 0); assert_eq!(vote.to_unit_vector(), vec![ Scalar::one(), Scalar::zero(), @@ -127,6 +174,7 @@ mod tests { ]); let vote = Vote::new(1, voting_options).unwrap(); + assert_eq!(vote.choice(), 1); assert_eq!(vote.to_unit_vector(), vec![ Scalar::zero(), Scalar::one(), @@ -134,6 +182,7 @@ mod tests { ]); let vote = Vote::new(2, voting_options).unwrap(); + assert_eq!(vote.choice(), 2); assert_eq!(vote.to_unit_vector(), vec![ Scalar::zero(), Scalar::zero(), diff --git a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs index f9a70b50e80..7e669ab25a2 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs @@ -9,18 +9,22 @@ use rand_core::CryptoRngCore; use super::{EncryptedVote, EncryptionRandomness, Vote}; use crate::{ crypto::{ + default_rng, group::{GroupElement, Scalar}, + hash::digest::{consts::U64, Digest}, zk_unit_vector::{generate_unit_vector_proof, verify_unit_vector_proof, UnitVectorProof}, }, - PublicKey, + vote_protocol::committee::ElectionPublicKey, }; /// Tally proof struct. #[allow(clippy::module_name_repetitions)] #[derive(Debug, Clone, PartialEq, Eq)] +#[must_use] pub struct VoterProof(pub(super) UnitVectorProof); /// Voter proof commitment struct. +#[must_use] pub struct VoterProofCommitment(GroupElement); impl VoterProofCommitment { @@ -28,6 +32,17 @@ impl VoterProofCommitment { pub fn random(rng: &mut R) -> Self { Self(GroupElement::GENERATOR.mul(&Scalar::random(rng))) } + + /// Randomly generate the `VoterProofCommitment` with the `crypto::default_rng`.. + pub fn random_with_default_rng() -> Self { + Self::random(&mut default_rng()) + } + + /// Generate a `VoterProofCommitment` from a hash digest. + pub fn from_hash(hash: D) -> VoterProofCommitment + where D: Digest + Default { + Self(GroupElement::from_hash(hash)) + } } /// Generates a voter proof. @@ -39,7 +54,7 @@ impl VoterProofCommitment { #[allow(clippy::module_name_repetitions)] pub fn generate_voter_proof( vote: &Vote, encrypted_vote: EncryptedVote, randomness: EncryptionRandomness, - public_key: &PublicKey, commitment: &VoterProofCommitment, rng: &mut R, + public_key: &ElectionPublicKey, commitment: &VoterProofCommitment, rng: &mut R, ) -> anyhow::Result { ensure!( vote.voting_options == encrypted_vote.0.len() && vote.voting_options == randomness.0.len(), @@ -54,22 +69,43 @@ pub fn generate_voter_proof( &vote.to_unit_vector(), encrypted_vote.0, randomness.0, - public_key, + &public_key.0, &commitment.0, rng, ); Ok(VoterProof(proof)) } +/// Generates a voter proof with `crypto::default_rng`. +/// More detailed described [here](https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/voting_transaction/crypto/#voters-proof) +/// +/// # Errors +/// - Provided arguments mismatch. Size of the provided `vote`, `encrypted_vote` and +/// `randomness` must be equal with each other. +#[allow(clippy::module_name_repetitions)] +pub fn generate_voter_proof_with_default_rng( + vote: &Vote, encrypted_vote: EncryptedVote, randomness: EncryptionRandomness, + public_key: &ElectionPublicKey, commitment: &VoterProofCommitment, +) -> anyhow::Result { + generate_voter_proof( + vote, + encrypted_vote, + randomness, + public_key, + commitment, + &mut default_rng(), + ) +} + /// Verifies a voter proof. /// More detailed described [here](https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/voting_transaction/crypto/#voters-proof) #[must_use] #[allow(clippy::module_name_repetitions)] pub fn verify_voter_proof( - encrypted_vote: EncryptedVote, public_key: &PublicKey, commitment: &VoterProofCommitment, - proof: &VoterProof, + encrypted_vote: EncryptedVote, public_key: &ElectionPublicKey, + commitment: &VoterProofCommitment, proof: &VoterProof, ) -> bool { - verify_unit_vector_proof(&proof.0, encrypted_vote.0, public_key, &commitment.0) + verify_unit_vector_proof(&proof.0, encrypted_vote.0, &public_key.0, &commitment.0) } #[cfg(test)] diff --git a/rust/catalyst-voting/tests/voting_test.rs b/rust/catalyst-voting/tests/voting_test.rs index 1cd5a05878d..e4b4242dd5b 100644 --- a/rust/catalyst-voting/tests/voting_test.rs +++ b/rust/catalyst-voting/tests/voting_test.rs @@ -1,19 +1,17 @@ //! A general voting integration test, which performs a full voting procedure. -use catalyst_voting::{ - vote_protocol::{ - tally::{ - decrypt_tally, - proof::{generate_tally_proof, verify_tally_proof}, - tally, DecryptionTallySetup, - }, - voter::{ - encrypt_vote, - proof::{generate_voter_proof, verify_voter_proof, VoterProofCommitment}, - Vote, - }, +use catalyst_voting::vote_protocol::{ + committee::ElectionSecretKey, + tally::{ + decrypt_tally, + proof::{generate_tally_proof_with_default_rng, verify_tally_proof}, + tally, DecryptionTallySetup, + }, + voter::{ + decrypt_vote, encrypt_vote_with_default_rng, + proof::{generate_voter_proof_with_default_rng, verify_voter_proof, VoterProofCommitment}, + Vote, }, - SecretKey, }; use proptest::prelude::ProptestConfig; use test_strategy::{proptest, Arbitrary}; @@ -30,11 +28,9 @@ struct Voter { #[proptest(ProptestConfig::with_cases(1))] fn voting_test(voters: [Voter; 100]) { - let mut rng = rand_core::OsRng; - - let election_secret_key = SecretKey::random(&mut rng); + let election_secret_key = ElectionSecretKey::random_with_default_rng(); let election_public_key = election_secret_key.public_key(); - let voter_proof_commitment = VoterProofCommitment::random(&mut rng); + let voter_proof_commitment = VoterProofCommitment::random_with_default_rng(); let votes: Vec<_> = voters .iter() @@ -43,9 +39,18 @@ fn voting_test(voters: [Voter; 100]) { let (encrypted_votes, randomness): (Vec<_>, Vec<_>) = votes .iter() - .map(|vote| encrypt_vote(vote, &election_public_key, &mut rng)) + .map(|vote| encrypt_vote_with_default_rng(vote, &election_public_key)) .unzip(); + // Decrypting votes + { + let decrypted_votes: Vec<_> = encrypted_votes + .iter() + .map(|v| decrypt_vote(v, &election_secret_key).unwrap()) + .collect(); + assert_eq!(votes, decrypted_votes); + } + // Verify encrypted votes { let voter_proofs: Vec<_> = votes @@ -53,13 +58,12 @@ fn voting_test(voters: [Voter; 100]) { .zip(encrypted_votes.iter()) .zip(randomness.iter()) .map(|((v, enc_v), r)| { - generate_voter_proof( + generate_voter_proof_with_default_rng( v, enc_v.clone(), r.clone(), &election_public_key, &voter_proof_commitment, - &mut rng, ) .unwrap() }) @@ -100,7 +104,7 @@ fn voting_test(voters: [Voter; 100]) { { let tally_proofs: Vec<_> = encrypted_tallies .iter() - .map(|t| generate_tally_proof(t, &election_secret_key, &mut rng)) + .map(|t| generate_tally_proof_with_default_rng(t, &election_secret_key)) .collect(); let is_ok = tally_proofs