From 8e2d1d46bd4f55a2ebad270e45693b9151f5ac93 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 9 Oct 2024 09:58:05 +0300 Subject: [PATCH 01/44] move vote protocol under the vote_protocol mod --- rust/catalyst-voting/src/lib.rs | 90 +----------------- rust/catalyst-voting/src/vote_protocol/mod.rs | 93 +++++++++++++++++++ .../src/{ => vote_protocol}/tally/mod.rs | 2 +- .../src/{ => vote_protocol}/tally/proof.rs | 0 .../src/{ => vote_protocol}/voter/mod.rs | 0 .../src/{ => vote_protocol}/voter/proof.rs | 0 rust/catalyst-voting/tests/voting_test.rs | 20 ++-- 7 files changed, 106 insertions(+), 99 deletions(-) create mode 100644 rust/catalyst-voting/src/vote_protocol/mod.rs rename rust/catalyst-voting/src/{ => vote_protocol}/tally/mod.rs (99%) rename rust/catalyst-voting/src/{ => vote_protocol}/tally/proof.rs (100%) rename rust/catalyst-voting/src/{ => vote_protocol}/voter/mod.rs (100%) rename rust/catalyst-voting/src/{ => vote_protocol}/voter/proof.rs (100%) diff --git a/rust/catalyst-voting/src/lib.rs b/rust/catalyst-voting/src/lib.rs index 16debee0455..14871d42f50 100644 --- a/rust/catalyst-voting/src/lib.rs +++ b/rust/catalyst-voting/src/lib.rs @@ -1,94 +1,6 @@ //! Voting primitives which are used among Catalyst ecosystem. -//! -//! ```rust -//! use catalyst_voting::{ -//! tally::{ -//! decrypt_tally, -//! proof::{generate_tally_proof, verify_tally_proof}, -//! tally, DecryptionTallySetup, -//! }, -//! voter::{encrypt_vote, Vote}, -//! SecretKey, -//! }; -//! -//! struct Voter { -//! voting_power: u64, -//! choice: usize, -//! } -//! -//! let mut rng = rand_core::OsRng; -//! let voting_options = 3; -//! let election_secret_key = SecretKey::random(&mut rng); -//! let election_public_key = election_secret_key.public_key(); -//! -//! let voter_1 = Voter { -//! voting_power: 10, -//! choice: 0, -//! }; -//! -//! let voter_2 = Voter { -//! voting_power: 20, -//! choice: 1, -//! }; -//! -//! let voter_3 = Voter { -//! voting_power: 30, -//! choice: 2, -//! }; -//! -//! 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); -//! let (encrypted_vote_2, voter_randomness_2) = -//! encrypt_vote(&vote_2, &election_public_key, &mut rng); -//! 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]; -//! -//! 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(); -//! -//! let tally_proofs: Vec<_> = encrypted_tallies -//! .iter() -//! .map(|t| generate_tally_proof(t, &election_secret_key, &mut rng)) -//! .collect(); -//! -//! let decryption_tally_setup = DecryptionTallySetup::new( -//! voter_1.voting_power + voter_2.voting_power + voter_3.voting_power, -//! ) -//! .unwrap(); -//! let decrypted_tallies: Vec<_> = encrypted_tallies -//! .iter() -//! .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); -//! -//! assert_eq!(decrypted_tallies, vec![ -//! voter_1.voting_power, -//! voter_2.voting_power, -//! voter_3.voting_power -//! ]); -//! ``` mod crypto; -pub mod tally; -pub mod voter; +pub mod vote_protocol; pub use crypto::elgamal::{PublicKey, SecretKey}; diff --git a/rust/catalyst-voting/src/vote_protocol/mod.rs b/rust/catalyst-voting/src/vote_protocol/mod.rs new file mode 100644 index 00000000000..16fb14d7bc3 --- /dev/null +++ b/rust/catalyst-voting/src/vote_protocol/mod.rs @@ -0,0 +1,93 @@ +//! 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, +//! }, +//! voter::{encrypt_vote, Vote}, +//! }, +//! SecretKey, +//! }; +//! +//! struct Voter { +//! voting_power: u64, +//! choice: usize, +//! } +//! +//! let mut rng = rand_core::OsRng; +//! let voting_options = 3; +//! let election_secret_key = SecretKey::random(&mut rng); +//! let election_public_key = election_secret_key.public_key(); +//! +//! let voter_1 = Voter { +//! voting_power: 10, +//! choice: 0, +//! }; +//! +//! let voter_2 = Voter { +//! voting_power: 20, +//! choice: 1, +//! }; +//! +//! let voter_3 = Voter { +//! voting_power: 30, +//! choice: 2, +//! }; +//! +//! 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); +//! let (encrypted_vote_2, voter_randomness_2) = +//! encrypt_vote(&vote_2, &election_public_key, &mut rng); +//! 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]; +//! +//! 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(); +//! +//! let tally_proofs: Vec<_> = encrypted_tallies +//! .iter() +//! .map(|t| generate_tally_proof(t, &election_secret_key, &mut rng)) +//! .collect(); +//! +//! let decryption_tally_setup = DecryptionTallySetup::new( +//! voter_1.voting_power + voter_2.voting_power + voter_3.voting_power, +//! ) +//! .unwrap(); +//! let decrypted_tallies: Vec<_> = encrypted_tallies +//! .iter() +//! .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); +//! +//! assert_eq!(decrypted_tallies, vec![ +//! voter_1.voting_power, +//! voter_2.voting_power, +//! voter_3.voting_power +//! ]); +//! ``` + +pub mod tally; +pub mod voter; diff --git a/rust/catalyst-voting/src/tally/mod.rs b/rust/catalyst-voting/src/vote_protocol/tally/mod.rs similarity index 99% rename from rust/catalyst-voting/src/tally/mod.rs rename to rust/catalyst-voting/src/vote_protocol/tally/mod.rs index 81cd10990ea..ee63f76ae31 100644 --- a/rust/catalyst-voting/src/tally/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/tally/mod.rs @@ -10,7 +10,7 @@ use crate::{ elgamal::{decrypt, Ciphertext, SecretKey}, group::Scalar, }, - voter::EncryptedVote, + vote_protocol::voter::EncryptedVote, }; /// An important decryption tally setup, which holds an important precomputed data needed diff --git a/rust/catalyst-voting/src/tally/proof.rs b/rust/catalyst-voting/src/vote_protocol/tally/proof.rs similarity index 100% rename from rust/catalyst-voting/src/tally/proof.rs rename to rust/catalyst-voting/src/vote_protocol/tally/proof.rs diff --git a/rust/catalyst-voting/src/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs similarity index 100% rename from rust/catalyst-voting/src/voter/mod.rs rename to rust/catalyst-voting/src/vote_protocol/voter/mod.rs diff --git a/rust/catalyst-voting/src/voter/proof.rs b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs similarity index 100% rename from rust/catalyst-voting/src/voter/proof.rs rename to rust/catalyst-voting/src/vote_protocol/voter/proof.rs diff --git a/rust/catalyst-voting/tests/voting_test.rs b/rust/catalyst-voting/tests/voting_test.rs index 75752d524b2..1cd5a05878d 100644 --- a/rust/catalyst-voting/tests/voting_test.rs +++ b/rust/catalyst-voting/tests/voting_test.rs @@ -1,15 +1,17 @@ //! A general voting integration test, which performs a full voting procedure. use catalyst_voting::{ - tally::{ - decrypt_tally, - proof::{generate_tally_proof, verify_tally_proof}, - tally, DecryptionTallySetup, - }, - voter::{ - encrypt_vote, - proof::{generate_voter_proof, verify_voter_proof, VoterProofCommitment}, - Vote, + 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, + }, }, SecretKey, }; From 6af5db80ae348fac72af7b6feb91197db7073c11 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 9 Oct 2024 11:25:24 +0300 Subject: [PATCH 02/44] add jormungandr tx struct --- rust/catalyst-voting/src/lib.rs | 1 + rust/catalyst-voting/src/txs/mod.rs | 4 + rust/catalyst-voting/src/txs/utils.rs | 3 + rust/catalyst-voting/src/txs/v1.rs | 119 ++++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 rust/catalyst-voting/src/txs/mod.rs create mode 100644 rust/catalyst-voting/src/txs/utils.rs create mode 100644 rust/catalyst-voting/src/txs/v1.rs diff --git a/rust/catalyst-voting/src/lib.rs b/rust/catalyst-voting/src/lib.rs index 14871d42f50..ac06b9e8dd8 100644 --- a/rust/catalyst-voting/src/lib.rs +++ b/rust/catalyst-voting/src/lib.rs @@ -1,6 +1,7 @@ //! Voting primitives which are used among Catalyst ecosystem. mod crypto; +pub mod txs; pub mod vote_protocol; pub use crypto::elgamal::{PublicKey, SecretKey}; diff --git a/rust/catalyst-voting/src/txs/mod.rs b/rust/catalyst-voting/src/txs/mod.rs new file mode 100644 index 00000000000..ed61e561416 --- /dev/null +++ b/rust/catalyst-voting/src/txs/mod.rs @@ -0,0 +1,4 @@ +//! A catalyst transaction objects implementation + +mod utils; +pub mod v1; diff --git a/rust/catalyst-voting/src/txs/utils.rs b/rust/catalyst-voting/src/txs/utils.rs new file mode 100644 index 00000000000..fec47ef7566 --- /dev/null +++ b/rust/catalyst-voting/src/txs/utils.rs @@ -0,0 +1,3 @@ +//! Utility functions for catalyst transactions module + +#![allow(missing_docs, clippy::missing_docs_in_private_items)] diff --git a/rust/catalyst-voting/src/txs/v1.rs b/rust/catalyst-voting/src/txs/v1.rs new file mode 100644 index 00000000000..7ea95157170 --- /dev/null +++ b/rust/catalyst-voting/src/txs/v1.rs @@ -0,0 +1,119 @@ +//! 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)] + +use std::io::Read; + +/// A v1 (Jörmungandr) transaction struct +pub struct Tx { + /// Vote plan id + vote_plan_id: [u8; 32], + /// Proposal index + proposal_index: u8, + /// Vote + vote: Vote, +} + +/// Vote struct +pub enum Vote { + /// Public voting choice + Public(u8), + /// Private (encrypted) voting choice + Private, +} + +/// `V1Tx` decoding error +#[derive(thiserror::Error, Debug)] +pub enum TxDecodingError { + /// Cannot decode tx size + #[error("Cannot decode `u32` tx size field.")] + CannotDecodeTxSize, + /// Cannot decode padding tag + #[error("Cannot decode `u8` padding tag field.")] + CannotDecodePaddingTag, + /// Invalid padding tag + #[error("Invalid padding tag field value, must be equals to `0`, provided: {0}.")] + InvalidPaddingTag(u8), + /// Cannot decode fragment tag + #[error("Cannot decode `u8` fragment tag field.")] + CannotDecodeFragmentTag, + /// Invalid fragment tag + #[error("Invalid fragment tag field value, must be equals to `11`, provided: {0}.")] + InvalidFragmentTag(u8), + /// Cannot decode vote plan id + #[error("Cannot decode vote plan id field.")] + CannotDecodeVotePlanId, + /// Cannot decode proposal index + #[error("Cannot decode proposal index field.")] + CannotDecodeProposalIndex, + /// Cannot decode vote tag + #[error("Cannot decode vote tag field.")] + CannotDecodeVoteTag, + /// Cannot decode vote tag + #[error("Invalid vote tag value, must be equals to `0` or `1`, provided: {0}")] + InvalidVoteTag(u8), + /// Cannot decode public vote + #[error("Cannot decode public vote field.")] + CannotDecodePublicVote, +} + +impl Tx { + /// Decode `V1Tx` from bytes. + /// + /// # Errors + /// - `TxDecodingError` + pub fn from_bytes(mut bytes: &[u8]) -> Result { + let mut u32_buf = [0u8; 4]; + let mut u8_buf = [0u8; 1]; + let mut u256_buf = [0u8; 32]; + + bytes + .read_exact(&mut u32_buf) + .map_err(|_| TxDecodingError::CannotDecodeTxSize)?; + let tx_size = u32::from_be_bytes(u32_buf); + + bytes + .read_exact(&mut u8_buf) + .map_err(|_| TxDecodingError::CannotDecodePaddingTag)?; + if u8_buf[0] != 0 { + return Err(TxDecodingError::InvalidPaddingTag(u8_buf[0])); + } + + bytes + .read_exact(&mut u8_buf) + .map_err(|_| TxDecodingError::CannotDecodeFragmentTag)?; + if u8_buf[0] != 11 { + return Err(TxDecodingError::InvalidFragmentTag(u8_buf[0])); + } + + bytes + .read_exact(&mut u256_buf) + .map_err(|_| TxDecodingError::CannotDecodeVotePlanId)?; + let vote_plan_id = u256_buf; + + bytes + .read_exact(&mut u8_buf) + .map_err(|_| TxDecodingError::CannotDecodeProposalIndex)?; + let proposal_index = u8_buf[0]; + + bytes + .read_exact(&mut u8_buf) + .map_err(|_| TxDecodingError::CannotDecodeVoteTag)?; + let vote = match u8_buf[0] { + 1 => { + bytes + .read_exact(&mut u8_buf) + .map_err(|_| TxDecodingError::CannotDecodePublicVote)?; + Vote::Public(u8_buf[0]) + }, + 2 => Vote::Private, + tag => return Err(TxDecodingError::InvalidVoteTag(tag)), + }; + + Ok(Self { + vote_plan_id, + proposal_index, + vote, + }) + } +} From 72351a1e52422ae1f81d74b5e34cf68abf63a7e3 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 9 Oct 2024 12:15:17 +0300 Subject: [PATCH 03/44] add CipherText serde --- rust/catalyst-voting/src/crypto/elgamal.rs | 32 +++++++++++++++++++ .../src/crypto/group/ristretto255.rs | 16 ++++++---- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/rust/catalyst-voting/src/crypto/elgamal.rs b/rust/catalyst-voting/src/crypto/elgamal.rs index 737c02a7d93..3fb1d6f2168 100644 --- a/rust/catalyst-voting/src/crypto/elgamal.rs +++ b/rust/catalyst-voting/src/crypto/elgamal.rs @@ -49,6 +49,9 @@ impl SecretKey { } impl Ciphertext { + /// `Ciphertext` bytes size + const BYTES_SIZE: usize = 64; + /// Generate a zero `Ciphertext`. /// The same as encrypt a `Scalar::zero()` message and `Scalar::zero()` randomness. pub fn zero() -> Self { @@ -64,6 +67,27 @@ impl Ciphertext { pub fn second(&self) -> &GroupElement { &self.1 } + + /// Convert this `Ciphertext` to its underlying sequence of bytes. + pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { + let mut res = [0; Self::BYTES_SIZE]; + res[0..Self::BYTES_SIZE / 2].copy_from_slice(&self.0.to_bytes()); + res[Self::BYTES_SIZE / 2..Self::BYTES_SIZE].copy_from_slice(&self.1.to_bytes()); + res + } + + /// Attempt to construct a `Scalar` from a compressed value byte representation. + #[allow(clippy::unwrap_used)] + pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> Option { + Some(Self( + GroupElement::from_bytes(bytes[0..Self::BYTES_SIZE / 2].try_into().unwrap())?, + GroupElement::from_bytes( + bytes[Self::BYTES_SIZE / 2..Self::BYTES_SIZE] + .try_into() + .unwrap(), + )?, + )) + } } /// Given a `message` represented as a `Scalar`, return a ciphertext using the @@ -116,6 +140,14 @@ mod tests { } } + #[proptest] + fn ciphertext_to_bytes_from_bytes_test(ge1: GroupElement, ge2: GroupElement) { + let c1 = Ciphertext(ge1, ge2); + let bytes = c1.to_bytes(); + let c2 = Ciphertext::from_bytes(&bytes).unwrap(); + assert_eq!(c1, c2); + } + #[proptest] fn ciphertext_add_test(e1: Scalar, e2: Scalar, e3: Scalar, e4: Scalar) { let g1 = GroupElement::GENERATOR.mul(&e1); diff --git a/rust/catalyst-voting/src/crypto/group/ristretto255.rs b/rust/catalyst-voting/src/crypto/group/ristretto255.rs index 71615aadbea..c65924efee5 100644 --- a/rust/catalyst-voting/src/crypto/group/ristretto255.rs +++ b/rust/catalyst-voting/src/crypto/group/ristretto255.rs @@ -37,6 +37,9 @@ impl Hash for GroupElement { } impl Scalar { + /// `Scalar` bytes size + pub const BYTES_SIZE: usize = 32; + /// Generate a random scalar value from the random number generator. pub fn random(rng: &mut R) -> Self { let mut scalar_bytes = [0u8; 64]; @@ -70,12 +73,12 @@ impl Scalar { } /// Convert this `Scalar` to its underlying sequence of bytes. - pub fn to_bytes(&self) -> [u8; 32] { + pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { self.0.to_bytes() } /// Attempt to construct a `Scalar` from a canonical byte representation. - pub fn from_bytes(bytes: [u8; 32]) -> Option { + pub fn from_bytes(bytes: [u8; Self::BYTES_SIZE]) -> Option { IScalar::from_canonical_bytes(bytes).map(Scalar).into() } @@ -87,6 +90,8 @@ impl Scalar { } impl GroupElement { + /// `GroupElement` bytes size + pub const BYTES_SIZE: usize = 32; /// ristretto255 group generator. pub const GENERATOR: GroupElement = GroupElement(RISTRETTO_BASEPOINT_POINT); @@ -97,12 +102,12 @@ impl GroupElement { /// Convert this `GroupElement` to its underlying sequence of bytes. /// Always encode the compressed value. - pub fn to_bytes(&self) -> [u8; 32] { + pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { self.0.compress().to_bytes() } /// Attempt to construct a `Scalar` from a compressed value byte representation. - pub fn from_bytes(bytes: &[u8; 32]) -> Option { + pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> Option { Some(GroupElement( CompressedRistretto::from_slice(bytes).ok()?.decompress()?, )) @@ -209,8 +214,7 @@ mod tests { } #[proptest] - fn group_element_to_bytes_from_bytes_test(e: Scalar) { - let ge1 = GroupElement::GENERATOR.mul(&e); + fn group_element_to_bytes_from_bytes_test(ge1: GroupElement) { let bytes = ge1.to_bytes(); let ge2 = GroupElement::from_bytes(&bytes).unwrap(); assert_eq!(ge1, ge2); From 0ee0f95c95fd1a3f4c9f3418a0170d87077b7c01 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 9 Oct 2024 14:23:41 +0300 Subject: [PATCH 04/44] add EncryptedVote decoding functionality --- rust/catalyst-voting/src/crypto/elgamal.rs | 16 ++++- rust/catalyst-voting/src/txs/v1.rs | 50 ++++++++++----- .../src/vote_protocol/voter/mod.rs | 62 +++++++++++++++++++ 3 files changed, 108 insertions(+), 20 deletions(-) diff --git a/rust/catalyst-voting/src/crypto/elgamal.rs b/rust/catalyst-voting/src/crypto/elgamal.rs index 3fb1d6f2168..520f3847091 100644 --- a/rust/catalyst-voting/src/crypto/elgamal.rs +++ b/rust/catalyst-voting/src/crypto/elgamal.rs @@ -50,7 +50,7 @@ impl SecretKey { impl Ciphertext { /// `Ciphertext` bytes size - const BYTES_SIZE: usize = 64; + pub const BYTES_SIZE: usize = GroupElement::BYTES_SIZE * 2; /// Generate a zero `Ciphertext`. /// The same as encrypt a `Scalar::zero()` message and `Scalar::zero()` randomness. @@ -140,9 +140,19 @@ mod tests { } } + impl Arbitrary for Ciphertext { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + any::<(GroupElement, GroupElement)>() + .prop_map(|(g1, g2)| Ciphertext(g1, g2)) + .boxed() + } + } + #[proptest] - fn ciphertext_to_bytes_from_bytes_test(ge1: GroupElement, ge2: GroupElement) { - let c1 = Ciphertext(ge1, ge2); + fn ciphertext_to_bytes_from_bytes_test(c1: Ciphertext) { let bytes = c1.to_bytes(); let c2 = Ciphertext::from_bytes(&bytes).unwrap(); assert_eq!(c1, c2); diff --git a/rust/catalyst-voting/src/txs/v1.rs b/rust/catalyst-voting/src/txs/v1.rs index 7ea95157170..5b3d090cf1b 100644 --- a/rust/catalyst-voting/src/txs/v1.rs +++ b/rust/catalyst-voting/src/txs/v1.rs @@ -4,6 +4,8 @@ use std::io::Read; +use crate::vote_protocol::voter::{DecodingError as EncryptedVoteDecodingError, EncryptedVote}; + /// A v1 (Jörmungandr) transaction struct pub struct Tx { /// Vote plan id @@ -19,12 +21,12 @@ pub enum Vote { /// Public voting choice Public(u8), /// Private (encrypted) voting choice - Private, + Private(EncryptedVote), } -/// `V1Tx` decoding error +/// `Tx` decoding error #[derive(thiserror::Error, Debug)] -pub enum TxDecodingError { +pub enum DecodingError { /// Cannot decode tx size #[error("Cannot decode `u32` tx size field.")] CannotDecodeTxSize, @@ -55,59 +57,73 @@ pub enum TxDecodingError { /// Cannot decode public vote #[error("Cannot decode public vote field.")] CannotDecodePublicVote, + /// Cannot decode ciphertexts array size + #[error("Cannot decode encrypted vote size field.")] + CannotDecodeEncryptedVoteSize, + /// Cannot decode encrypted vote + #[error(transparent)] + CannotDecodeEncryptedVote(#[from] EncryptedVoteDecodingError), } impl Tx { - /// Decode `V1Tx` from bytes. + /// Decode `Tx` from bytes. /// /// # Errors - /// - `TxDecodingError` - pub fn from_bytes(mut bytes: &[u8]) -> Result { + /// - `DecodingError` + pub fn from_bytes(mut bytes: &[u8]) -> Result { let mut u32_buf = [0u8; 4]; let mut u8_buf = [0u8; 1]; let mut u256_buf = [0u8; 32]; + // let mut u512_buf = [0u8; 64]; bytes .read_exact(&mut u32_buf) - .map_err(|_| TxDecodingError::CannotDecodeTxSize)?; + .map_err(|_| DecodingError::CannotDecodeTxSize)?; let tx_size = u32::from_be_bytes(u32_buf); bytes .read_exact(&mut u8_buf) - .map_err(|_| TxDecodingError::CannotDecodePaddingTag)?; + .map_err(|_| DecodingError::CannotDecodePaddingTag)?; if u8_buf[0] != 0 { - return Err(TxDecodingError::InvalidPaddingTag(u8_buf[0])); + return Err(DecodingError::InvalidPaddingTag(u8_buf[0])); } bytes .read_exact(&mut u8_buf) - .map_err(|_| TxDecodingError::CannotDecodeFragmentTag)?; + .map_err(|_| DecodingError::CannotDecodeFragmentTag)?; if u8_buf[0] != 11 { - return Err(TxDecodingError::InvalidFragmentTag(u8_buf[0])); + return Err(DecodingError::InvalidFragmentTag(u8_buf[0])); } bytes .read_exact(&mut u256_buf) - .map_err(|_| TxDecodingError::CannotDecodeVotePlanId)?; + .map_err(|_| DecodingError::CannotDecodeVotePlanId)?; let vote_plan_id = u256_buf; bytes .read_exact(&mut u8_buf) - .map_err(|_| TxDecodingError::CannotDecodeProposalIndex)?; + .map_err(|_| DecodingError::CannotDecodeProposalIndex)?; let proposal_index = u8_buf[0]; bytes .read_exact(&mut u8_buf) - .map_err(|_| TxDecodingError::CannotDecodeVoteTag)?; + .map_err(|_| DecodingError::CannotDecodeVoteTag)?; let vote = match u8_buf[0] { 1 => { bytes .read_exact(&mut u8_buf) - .map_err(|_| TxDecodingError::CannotDecodePublicVote)?; + .map_err(|_| DecodingError::CannotDecodePublicVote)?; Vote::Public(u8_buf[0]) }, - 2 => Vote::Private, - tag => return Err(TxDecodingError::InvalidVoteTag(tag)), + 2 => { + bytes + .read_exact(&mut u8_buf) + .map_err(|_| DecodingError::CannotDecodeEncryptedVoteSize)?; + let encrypted_vote = EncryptedVote::from_bytes(bytes, u8_buf[0].into())?; + + Vote::Private(encrypted_vote) + }, + tag => return Err(DecodingError::InvalidVoteTag(tag)), }; Ok(Self { diff --git a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index e22d777c3ab..32271af5ec6 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -2,6 +2,8 @@ pub mod proof; +use std::io::Read; + use rand_core::CryptoRngCore; use crate::crypto::{ @@ -36,7 +38,53 @@ impl EncryptionRandomness { } } +/// `Tx` decoding error +#[derive(thiserror::Error, Debug)] +pub enum DecodingError { + /// Cannot decode ciphertexts array size + #[error("Cannot decode ciphertexts array size field.")] + CannotDecodeCiphertextArraySize, + /// Cannot decode ciphertext + #[error("Cannot decode ciphertext field.")] + CannotDecodeCiphertext, +} + impl EncryptedVote { + /// Decode `EncryptedVote` from bytes. + /// + /// # Errors + /// - `DecodingError` + pub fn from_bytes(mut bytes: &[u8], size: usize) -> Result { + let mut u512_buf = [0u8; 64]; + + let mut ciphertexts = Vec::with_capacity(size); + for _ in 0..size { + bytes + .read_exact(&mut u512_buf) + .map_err(|_| DecodingError::CannotDecodeCiphertext)?; + ciphertexts.push( + Ciphertext::from_bytes(&u512_buf).ok_or(DecodingError::CannotDecodeCiphertext)?, + ); + } + Ok(Self(ciphertexts)) + } + + /// Get a deserialized bytes size + #[must_use] + pub fn bytes_size(&self) -> usize { + self.0.len() * Ciphertext::BYTES_SIZE + } + + /// Encode `EncryptedVote` tos bytes. + #[must_use] + pub fn to_bytes(&self) -> Vec { + let mut res = Vec::with_capacity(self.bytes_size()); + self.0 + .iter() + .for_each(|c| res.extend_from_slice(&c.to_bytes())); + res + } + /// Get the ciphertext to the corresponding `voting_option`. pub(crate) fn get_ciphertext_for_choice(&self, voting_option: usize) -> Option<&Ciphertext> { self.0.get(voting_option) @@ -106,8 +154,22 @@ pub fn encrypt_vote( #[cfg(test)] mod tests { + use proptest::sample::size_range; + use test_strategy::proptest; + use super::*; + #[proptest] + fn encrypted_vote_to_bytes_from_bytes_test( + #[any(size_range(0..u8::MAX as usize).lift())] ciphers: Vec, + ) { + let vote1 = EncryptedVote(ciphers); + let bytes = vote1.to_bytes(); + assert_eq!(bytes.len(), vote1.bytes_size()); + let vote2 = EncryptedVote::from_bytes(&bytes, vote1.0.len()).unwrap(); + assert_eq!(vote1, vote2); + } + #[test] fn vote_test() { let voting_options = 3; From fad9fd7a5434c348bc247d942bb338dbe74ae247 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 9 Oct 2024 14:53:33 +0300 Subject: [PATCH 05/44] add new deserializers --- rust/catalyst-voting/src/crypto/elgamal.rs | 12 +- .../randomness_announcements.rs | 116 +++++++++++++++++- .../src/vote_protocol/voter/mod.rs | 5 +- 3 files changed, 119 insertions(+), 14 deletions(-) diff --git a/rust/catalyst-voting/src/crypto/elgamal.rs b/rust/catalyst-voting/src/crypto/elgamal.rs index 520f3847091..3852e25d4ec 100644 --- a/rust/catalyst-voting/src/crypto/elgamal.rs +++ b/rust/catalyst-voting/src/crypto/elgamal.rs @@ -71,8 +71,8 @@ impl Ciphertext { /// Convert this `Ciphertext` to its underlying sequence of bytes. pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { let mut res = [0; Self::BYTES_SIZE]; - res[0..Self::BYTES_SIZE / 2].copy_from_slice(&self.0.to_bytes()); - res[Self::BYTES_SIZE / 2..Self::BYTES_SIZE].copy_from_slice(&self.1.to_bytes()); + res[0..32].copy_from_slice(&self.0.to_bytes()); + res[32..64].copy_from_slice(&self.1.to_bytes()); res } @@ -80,12 +80,8 @@ impl Ciphertext { #[allow(clippy::unwrap_used)] pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> Option { Some(Self( - GroupElement::from_bytes(bytes[0..Self::BYTES_SIZE / 2].try_into().unwrap())?, - GroupElement::from_bytes( - bytes[Self::BYTES_SIZE / 2..Self::BYTES_SIZE] - .try_into() - .unwrap(), - )?, + GroupElement::from_bytes(bytes[0..32].try_into().unwrap())?, + GroupElement::from_bytes(bytes[32..64].try_into().unwrap())?, )) } } diff --git a/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs b/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs index 38951ead3b9..0132e6bb105 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs @@ -1,6 +1,6 @@ //! Randomness and announcements structs for the ZK unit vector algorithm -#![allow(clippy::missing_docs_in_private_items)] +#![allow(clippy::missing_docs_in_private_items, dead_code)] use std::ops::Mul; @@ -9,7 +9,7 @@ use rand_core::CryptoRngCore; use crate::crypto::group::{GroupElement, Scalar}; /// Randomness generated in the proof, used for the hiding property. -#[derive(Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct BlindingRandomness { pub(crate) alpha: Scalar, pub(crate) betta: Scalar, @@ -30,13 +30,25 @@ impl BlindingRandomness { /// First announcement, formed by I, B, A group elements. These group elements /// are the commitments of the binary representation of the unit vector index. +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Announcement { pub(crate) i: GroupElement, pub(crate) b: GroupElement, pub(crate) a: GroupElement, } +/// `EncryptedVote` decoding error +#[derive(thiserror::Error, Debug)] +pub enum AnnouncementDecodingError { + /// Cannot decode ciphertext + #[error("Cannot decode group element {0} field.")] + CannotDecodeGroupElement(char), +} + impl Announcement { + /// `Announcement` bytes size + pub const BYTES_SIZE: usize = GroupElement::BYTES_SIZE * 3; + pub(crate) fn new( i_bit: bool, rand: &BlindingRandomness, commitment_key: &GroupElement, ) -> Self { @@ -53,6 +65,31 @@ impl Announcement { }; Self { i, b, a } } + + /// Decode `Announcement` from bytes. + /// + /// # Errors + /// - `AnnouncementDecodingError` + #[allow(clippy::unwrap_used)] + pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> Result { + let i = GroupElement::from_bytes(bytes[0..32].try_into().unwrap()) + .ok_or(AnnouncementDecodingError::CannotDecodeGroupElement('i'))?; + let b = GroupElement::from_bytes(bytes[32..64].try_into().unwrap()) + .ok_or(AnnouncementDecodingError::CannotDecodeGroupElement('b'))?; + let a = GroupElement::from_bytes(bytes[64..96].try_into().unwrap()) + .ok_or(AnnouncementDecodingError::CannotDecodeGroupElement('a'))?; + Ok(Self { i, b, a }) + } + + /// Encode `Announcement` tos bytes. + #[must_use] + pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { + let mut res = [0; 96]; + res[0..32].copy_from_slice(&self.i.to_bytes()); + res[32..64].copy_from_slice(&self.b.to_bytes()); + res[64..96].copy_from_slice(&self.a.to_bytes()); + res + } } /// Response encoding the bits of the private vector, and the randomness of @@ -64,7 +101,18 @@ pub struct ResponseRandomness { pub(crate) v: Scalar, } +/// `EncryptedVote` decoding error +#[derive(thiserror::Error, Debug)] +pub enum ResponseRandomnessDecodingError { + /// Cannot decode ciphertext + #[error("Cannot decode scalar {0} field.")] + CannotDecodeScalar(char), +} + impl ResponseRandomness { + /// `ResponseRandomness` bytes size + pub const BYTES_SIZE: usize = Scalar::BYTES_SIZE * 3; + pub(crate) fn new(i_bit: bool, rand: &BlindingRandomness, com_2: &Scalar) -> Self { let z = if i_bit { com_2 + &rand.betta @@ -75,6 +123,33 @@ impl ResponseRandomness { let v = &(&rand.alpha * &(com_2 - &z)) + &rand.delta; Self { z, w, v } } + + /// Decode `ResponseRandomness` from bytes. + /// + /// # Errors + /// - `ResponseRandomnessDecodingError` + #[allow(clippy::unwrap_used)] + pub fn from_bytes( + bytes: &[u8; Self::BYTES_SIZE], + ) -> Result { + let z = Scalar::from_bytes(bytes[0..32].try_into().unwrap()) + .ok_or(ResponseRandomnessDecodingError::CannotDecodeScalar('z'))?; + let w = Scalar::from_bytes(bytes[32..64].try_into().unwrap()) + .ok_or(ResponseRandomnessDecodingError::CannotDecodeScalar('w'))?; + let v = Scalar::from_bytes(bytes[64..96].try_into().unwrap()) + .ok_or(ResponseRandomnessDecodingError::CannotDecodeScalar('v'))?; + Ok(Self { z, w, v }) + } + + /// Encode `ResponseRandomness` tos bytes. + #[must_use] + pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { + let mut res = [0; 96]; + res[0..32].copy_from_slice(&self.z.to_bytes()); + res[32..64].copy_from_slice(&self.w.to_bytes()); + res[64..96].copy_from_slice(&self.v.to_bytes()); + res + } } #[cfg(test)] @@ -83,6 +158,7 @@ mod tests { arbitrary::any, prelude::{Arbitrary, BoxedStrategy, Strategy}, }; + use test_strategy::proptest; use super::*; @@ -103,4 +179,40 @@ mod tests { .boxed() } } + + impl Arbitrary for Announcement { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + any::<(GroupElement, GroupElement, GroupElement)>() + .prop_map(|(i, b, a)| Announcement { i, b, a }) + .boxed() + } + } + + impl Arbitrary for ResponseRandomness { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + any::<(Scalar, Scalar, Scalar)>() + .prop_map(|(z, w, v)| ResponseRandomness { z, w, v }) + .boxed() + } + } + + #[proptest] + fn announcement_to_bytes_from_bytes_test(a1: Announcement) { + let bytes = a1.to_bytes(); + let a2 = Announcement::from_bytes(&bytes).unwrap(); + assert_eq!(a1, a2); + } + + #[proptest] + fn response_randomness_to_bytes_from_bytes_test(r1: ResponseRandomness) { + let bytes = r1.to_bytes(); + let r2 = ResponseRandomness::from_bytes(&bytes).unwrap(); + assert_eq!(r1, r2); + } } diff --git a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index 32271af5ec6..de3aa1a2351 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -38,12 +38,9 @@ impl EncryptionRandomness { } } -/// `Tx` decoding error +/// `EncryptedVote` decoding error #[derive(thiserror::Error, Debug)] pub enum DecodingError { - /// Cannot decode ciphertexts array size - #[error("Cannot decode ciphertexts array size field.")] - CannotDecodeCiphertextArraySize, /// Cannot decode ciphertext #[error("Cannot decode ciphertext field.")] CannotDecodeCiphertext, From c3e32135282ee8f05ba7b32e276efd379047f2f5 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 9 Oct 2024 15:21:10 +0300 Subject: [PATCH 06/44] refactor --- .../src/vote_protocol/voter/mod.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index de3aa1a2351..b587590571f 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -42,8 +42,8 @@ impl EncryptionRandomness { #[derive(thiserror::Error, Debug)] pub enum DecodingError { /// Cannot decode ciphertext - #[error("Cannot decode ciphertext field.")] - CannotDecodeCiphertext, + #[error("Cannot decode ciphertext {0} field.")] + CannotDecodeCiphertext(usize), } impl EncryptedVote { @@ -52,15 +52,16 @@ impl EncryptedVote { /// # Errors /// - `DecodingError` pub fn from_bytes(mut bytes: &[u8], size: usize) -> Result { - let mut u512_buf = [0u8; 64]; + let mut ciph_buf = [0u8; Ciphertext::BYTES_SIZE]; let mut ciphertexts = Vec::with_capacity(size); - for _ in 0..size { + for i in 0..size { bytes - .read_exact(&mut u512_buf) - .map_err(|_| DecodingError::CannotDecodeCiphertext)?; + .read_exact(&mut ciph_buf) + .map_err(|_| DecodingError::CannotDecodeCiphertext(i))?; ciphertexts.push( - Ciphertext::from_bytes(&u512_buf).ok_or(DecodingError::CannotDecodeCiphertext)?, + Ciphertext::from_bytes(&ciph_buf) + .ok_or(DecodingError::CannotDecodeCiphertext(i))?, ); } Ok(Self(ciphertexts)) From f37999acbed1b03305777eceab4763273b6a902e Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 9 Oct 2024 20:05:05 +0300 Subject: [PATCH 07/44] wip --- rust/catalyst-voting/src/crypto/mod.rs | 12 +-- .../src/crypto/zk_unit_vector/mod.rs | 91 ++++++++++++++++++- .../randomness_announcements.rs | 46 +++------- rust/catalyst-voting/src/txs/v1.rs | 9 +- .../src/vote_protocol/voter/mod.rs | 31 ++----- .../src/vote_protocol/voter/proof.rs | 10 +- 6 files changed, 129 insertions(+), 70 deletions(-) diff --git a/rust/catalyst-voting/src/crypto/mod.rs b/rust/catalyst-voting/src/crypto/mod.rs index 42ef8bbdd0d..32efae43415 100644 --- a/rust/catalyst-voting/src/crypto/mod.rs +++ b/rust/catalyst-voting/src/crypto/mod.rs @@ -1,8 +1,8 @@ //! Crypto primitives which are used by voting protocol. -pub(crate) mod babystep_giantstep; -pub(crate) mod elgamal; -pub(crate) mod group; -pub(crate) mod hash; -pub(crate) mod zk_dl_equality; -pub(crate) mod zk_unit_vector; +pub mod babystep_giantstep; +pub mod elgamal; +pub mod group; +pub mod hash; +pub mod zk_dl_equality; +pub mod zk_unit_vector; 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 8dd849864b7..aded9035f04 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs @@ -11,7 +11,7 @@ mod polynomial; mod randomness_announcements; mod utils; -use std::ops::Mul; +use std::{io::Read, ops::Mul}; use challenges::{calculate_first_challenge_hash, calculate_second_challenge_hash}; use polynomial::{calculate_polynomial_val, generate_polynomial, Polynomial}; @@ -25,6 +25,7 @@ use crate::crypto::{ }; /// Unit vector proof struct +#[derive(Debug, Clone, PartialEq, Eq)] pub struct UnitVectorProof( Vec, Vec, @@ -32,6 +33,64 @@ pub struct UnitVectorProof( Scalar, ); +impl UnitVectorProof { + /// Decode `UnitVectorProof` from bytes. + pub fn from_bytes(mut bytes: &[u8], size: usize) -> Option { + let mut ann_buf = [0u8; Announcement::BYTES_SIZE]; + let mut dl_buf = [0u8; Ciphertext::BYTES_SIZE]; + let mut rr_buf = [0u8; ResponseRandomness::BYTES_SIZE]; + + let ann = (0..size) + .map(|_| { + bytes.read_exact(&mut ann_buf).ok()?; + Announcement::from_bytes(&ann_buf) + }) + .collect::>()?; + let dl = (0..size) + .map(|_| { + bytes.read_exact(&mut dl_buf).ok()?; + Ciphertext::from_bytes(&dl_buf) + }) + .collect::>()?; + let rr = (0..size) + .map(|_| { + bytes.read_exact(&mut rr_buf).ok()?; + ResponseRandomness::from_bytes(&rr_buf) + }) + .collect::>()?; + + let mut scalar_buf = [0u8; Scalar::BYTES_SIZE]; + bytes.read_exact(&mut scalar_buf).ok()?; + let scalar = Scalar::from_bytes(scalar_buf)?; + Some(Self(ann, dl, rr, scalar)) + } + + /// Get a deserialized bytes size + #[must_use] + pub fn bytes_size(&self) -> usize { + self.0.len() * Announcement::BYTES_SIZE + + self.0.len() * Ciphertext::BYTES_SIZE + + self.0.len() * ResponseRandomness::BYTES_SIZE + } + + /// Encode `EncryptedVote` tos bytes. + #[must_use] + pub fn to_bytes(&self) -> Vec { + let mut res = Vec::with_capacity(self.bytes_size()); + self.0 + .iter() + .for_each(|c| res.extend_from_slice(&c.to_bytes())); + self.1 + .iter() + .for_each(|c| res.extend_from_slice(&c.to_bytes())); + self.2 + .iter() + .for_each(|c| res.extend_from_slice(&c.to_bytes())); + res.extend_from_slice(&self.3.to_bytes()); + res + } +} + /// Generates a unit vector proof. /// /// `unit_vector` must be a collection of `Scalar` where only one element is equal to @@ -232,12 +291,40 @@ fn check_2( #[cfg(test)] mod tests { - use proptest::sample::size_range; + use proptest::{ + prelude::{any_with, Arbitrary, BoxedStrategy, Strategy}, + sample::size_range, + }; use rand_core::OsRng; use test_strategy::proptest; use super::{super::elgamal::SecretKey, *}; + impl Arbitrary for UnitVectorProof { + type Parameters = usize; + type Strategy = BoxedStrategy; + + fn arbitrary_with(size: Self::Parameters) -> Self::Strategy { + any_with::<( + Vec<((Announcement, Ciphertext), ResponseRandomness)>, + Scalar, + )>(((size_range(size), (((), ()), ())), ())) + .prop_map(|(val, scalar)| { + let (vec, rr): (Vec<_>, Vec<_>) = val.into_iter().unzip(); + let (an, ciph) = vec.into_iter().unzip(); + Self(an, ciph, rr, scalar) + }) + .boxed() + } + } + + #[proptest] + fn proof_to_bytes_from_bytes_test(p1: UnitVectorProof) { + let bytes = p1.to_bytes(); + let p2 = UnitVectorProof::from_bytes(&bytes, p1.0.len()).unwrap(); + assert_eq!(p1, p2); + } + fn is_unit_vector(vector: &[Scalar]) -> bool { let ones = vector.iter().filter(|s| s == &&Scalar::one()).count(); let zeros = vector.iter().filter(|s| s == &&Scalar::zero()).count(); diff --git a/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs b/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs index 0132e6bb105..460b87205f2 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs @@ -1,6 +1,6 @@ //! Randomness and announcements structs for the ZK unit vector algorithm -#![allow(clippy::missing_docs_in_private_items, dead_code)] +#![allow(clippy::missing_docs_in_private_items)] use std::ops::Mul; @@ -37,14 +37,6 @@ pub struct Announcement { pub(crate) a: GroupElement, } -/// `EncryptedVote` decoding error -#[derive(thiserror::Error, Debug)] -pub enum AnnouncementDecodingError { - /// Cannot decode ciphertext - #[error("Cannot decode group element {0} field.")] - CannotDecodeGroupElement(char), -} - impl Announcement { /// `Announcement` bytes size pub const BYTES_SIZE: usize = GroupElement::BYTES_SIZE * 3; @@ -71,14 +63,11 @@ impl Announcement { /// # Errors /// - `AnnouncementDecodingError` #[allow(clippy::unwrap_used)] - pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> Result { - let i = GroupElement::from_bytes(bytes[0..32].try_into().unwrap()) - .ok_or(AnnouncementDecodingError::CannotDecodeGroupElement('i'))?; - let b = GroupElement::from_bytes(bytes[32..64].try_into().unwrap()) - .ok_or(AnnouncementDecodingError::CannotDecodeGroupElement('b'))?; - let a = GroupElement::from_bytes(bytes[64..96].try_into().unwrap()) - .ok_or(AnnouncementDecodingError::CannotDecodeGroupElement('a'))?; - Ok(Self { i, b, a }) + pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> Option { + let i = GroupElement::from_bytes(bytes[0..32].try_into().unwrap())?; + let b = GroupElement::from_bytes(bytes[32..64].try_into().unwrap())?; + let a = GroupElement::from_bytes(bytes[64..96].try_into().unwrap())?; + Some(Self { i, b, a }) } /// Encode `Announcement` tos bytes. @@ -101,14 +90,6 @@ pub struct ResponseRandomness { pub(crate) v: Scalar, } -/// `EncryptedVote` decoding error -#[derive(thiserror::Error, Debug)] -pub enum ResponseRandomnessDecodingError { - /// Cannot decode ciphertext - #[error("Cannot decode scalar {0} field.")] - CannotDecodeScalar(char), -} - impl ResponseRandomness { /// `ResponseRandomness` bytes size pub const BYTES_SIZE: usize = Scalar::BYTES_SIZE * 3; @@ -129,16 +110,11 @@ impl ResponseRandomness { /// # Errors /// - `ResponseRandomnessDecodingError` #[allow(clippy::unwrap_used)] - pub fn from_bytes( - bytes: &[u8; Self::BYTES_SIZE], - ) -> Result { - let z = Scalar::from_bytes(bytes[0..32].try_into().unwrap()) - .ok_or(ResponseRandomnessDecodingError::CannotDecodeScalar('z'))?; - let w = Scalar::from_bytes(bytes[32..64].try_into().unwrap()) - .ok_or(ResponseRandomnessDecodingError::CannotDecodeScalar('w'))?; - let v = Scalar::from_bytes(bytes[64..96].try_into().unwrap()) - .ok_or(ResponseRandomnessDecodingError::CannotDecodeScalar('v'))?; - Ok(Self { z, w, v }) + pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> Option { + let z = Scalar::from_bytes(bytes[0..32].try_into().unwrap())?; + let w = Scalar::from_bytes(bytes[32..64].try_into().unwrap())?; + let v = Scalar::from_bytes(bytes[64..96].try_into().unwrap())?; + Some(Self { z, w, v }) } /// Encode `ResponseRandomness` tos bytes. diff --git a/rust/catalyst-voting/src/txs/v1.rs b/rust/catalyst-voting/src/txs/v1.rs index 5b3d090cf1b..b3e86b1d01d 100644 --- a/rust/catalyst-voting/src/txs/v1.rs +++ b/rust/catalyst-voting/src/txs/v1.rs @@ -4,7 +4,7 @@ use std::io::Read; -use crate::vote_protocol::voter::{DecodingError as EncryptedVoteDecodingError, EncryptedVote}; +use crate::vote_protocol::voter::EncryptedVote; /// A v1 (Jörmungandr) transaction struct pub struct Tx { @@ -61,8 +61,8 @@ pub enum DecodingError { #[error("Cannot decode encrypted vote size field.")] CannotDecodeEncryptedVoteSize, /// Cannot decode encrypted vote - #[error(transparent)] - CannotDecodeEncryptedVote(#[from] EncryptedVoteDecodingError), + #[error("Cannot decode ecnrypted vote field.")] + CannotDecodeEncryptedVote, } impl Tx { @@ -119,7 +119,8 @@ impl Tx { bytes .read_exact(&mut u8_buf) .map_err(|_| DecodingError::CannotDecodeEncryptedVoteSize)?; - let encrypted_vote = EncryptedVote::from_bytes(bytes, u8_buf[0].into())?; + let encrypted_vote = EncryptedVote::from_bytes(bytes, u8_buf[0].into()) + .ok_or(DecodingError::CannotDecodeEncryptedVote)?; Vote::Private(encrypted_vote) }, diff --git a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index b587590571f..1f377bc7ff6 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -38,33 +38,20 @@ impl EncryptionRandomness { } } -/// `EncryptedVote` decoding error -#[derive(thiserror::Error, Debug)] -pub enum DecodingError { - /// Cannot decode ciphertext - #[error("Cannot decode ciphertext {0} field.")] - CannotDecodeCiphertext(usize), -} - impl EncryptedVote { /// Decode `EncryptedVote` from bytes. - /// - /// # Errors - /// - `DecodingError` - pub fn from_bytes(mut bytes: &[u8], size: usize) -> Result { + #[must_use] + pub fn from_bytes(mut bytes: &[u8], size: usize) -> Option { let mut ciph_buf = [0u8; Ciphertext::BYTES_SIZE]; - let mut ciphertexts = Vec::with_capacity(size); - for i in 0..size { - bytes - .read_exact(&mut ciph_buf) - .map_err(|_| DecodingError::CannotDecodeCiphertext(i))?; - ciphertexts.push( + let ciphertexts = (0..size) + .map(|_| { + bytes.read_exact(&mut ciph_buf).ok()?; Ciphertext::from_bytes(&ciph_buf) - .ok_or(DecodingError::CannotDecodeCiphertext(i))?, - ); - } - Ok(Self(ciphertexts)) + }) + .collect::>()?; + + Some(Self(ciphertexts)) } /// Get a deserialized bytes size diff --git a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs index 8df75db4b0d..f9550ba6aa4 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs @@ -1,7 +1,7 @@ //! Voter proof generation and verification procedures. //! It allows to transparently verify the correctness voter generation and encryption. -use std::ops::Mul; +use std::ops::{Deref, Mul}; use rand_core::CryptoRngCore; @@ -18,6 +18,14 @@ use crate::{ #[allow(clippy::module_name_repetitions)] pub struct VoterProof(UnitVectorProof); +impl Deref for VoterProof { + type Target = UnitVectorProof; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + /// Voter proof commitment struct. pub struct VoterProofCommitment(GroupElement); From 5b35061b66769748f05e6bfe8b83147b799e42c2 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 10 Oct 2024 09:47:32 +0300 Subject: [PATCH 08/44] wip --- rust/catalyst-voting/src/txs/v1.rs | 109 +++++++++--------- .../src/vote_protocol/voter/proof.rs | 39 ++++++- 2 files changed, 90 insertions(+), 58 deletions(-) diff --git a/rust/catalyst-voting/src/txs/v1.rs b/rust/catalyst-voting/src/txs/v1.rs index b3e86b1d01d..a22bd00db38 100644 --- a/rust/catalyst-voting/src/txs/v1.rs +++ b/rust/catalyst-voting/src/txs/v1.rs @@ -4,7 +4,7 @@ use std::io::Read; -use crate::vote_protocol::voter::EncryptedVote; +use crate::vote_protocol::voter::{proof::VoterProof, EncryptedVote}; /// A v1 (Jörmungandr) transaction struct pub struct Tx { @@ -21,48 +21,39 @@ pub enum Vote { /// Public voting choice Public(u8), /// Private (encrypted) voting choice - Private(EncryptedVote), + Private(EncryptedVote, VoterProof), } /// `Tx` decoding error #[derive(thiserror::Error, Debug)] pub enum DecodingError { - /// Cannot decode tx size - #[error("Cannot decode `u32` tx size field.")] - CannotDecodeTxSize, - /// Cannot decode padding tag - #[error("Cannot decode `u8` padding tag field.")] - CannotDecodePaddingTag, + /// `std::io::Error` + #[error(transparent)] + IoRead(#[from] std::io::Error), /// Invalid padding tag #[error("Invalid padding tag field value, must be equals to `0`, provided: {0}.")] InvalidPaddingTag(u8), - /// Cannot decode fragment tag - #[error("Cannot decode `u8` fragment tag field.")] - CannotDecodeFragmentTag, /// Invalid fragment tag #[error("Invalid fragment tag field value, must be equals to `11`, provided: {0}.")] InvalidFragmentTag(u8), - /// Cannot decode vote plan id - #[error("Cannot decode vote plan id field.")] - CannotDecodeVotePlanId, - /// Cannot decode proposal index - #[error("Cannot decode proposal index field.")] - CannotDecodeProposalIndex, - /// Cannot decode vote tag - #[error("Cannot decode vote tag field.")] - CannotDecodeVoteTag, /// Cannot decode vote tag #[error("Invalid vote tag value, must be equals to `0` or `1`, provided: {0}")] InvalidVoteTag(u8), - /// Cannot decode public vote - #[error("Cannot decode public vote field.")] - CannotDecodePublicVote, - /// Cannot decode ciphertexts array size - #[error("Cannot decode encrypted vote size field.")] - CannotDecodeEncryptedVoteSize, /// Cannot decode encrypted vote #[error("Cannot decode ecnrypted vote field.")] CannotDecodeEncryptedVote, + /// Cannot decode voter proof field + #[error("Cannot decode voter proof field.")] + CannotDecodeVoterProof, + /// Invalid number of inputs + #[error("Invalid number of inputs, expected: `1`, provided: {0}")] + InvalidNumberOfInputs(u8), + /// Invalid number of outputs + #[error("Invalid number of outputs, expected: `0`, provided: {0}")] + InvalidNumberOfOutputs(u8), + /// Invalid input tag + #[error("Invalid input tag, expected: `255`, provided: {0}")] + InvalidInputTag(u8), } impl Tx { @@ -70,63 +61,75 @@ impl Tx { /// /// # Errors /// - `DecodingError` + #[allow(clippy::indexing_slicing)] pub fn from_bytes(mut bytes: &[u8]) -> Result { - let mut u32_buf = [0u8; 4]; let mut u8_buf = [0u8; 1]; + let mut u32_buf = [0u8; 4]; + let mut u64_buf = [0u8; 8]; let mut u256_buf = [0u8; 32]; // let mut u512_buf = [0u8; 64]; - bytes - .read_exact(&mut u32_buf) - .map_err(|_| DecodingError::CannotDecodeTxSize)?; + bytes.read_exact(&mut u32_buf)?; let tx_size = u32::from_be_bytes(u32_buf); - bytes - .read_exact(&mut u8_buf) - .map_err(|_| DecodingError::CannotDecodePaddingTag)?; + bytes.read_exact(&mut u8_buf)?; if u8_buf[0] != 0 { return Err(DecodingError::InvalidPaddingTag(u8_buf[0])); } - bytes - .read_exact(&mut u8_buf) - .map_err(|_| DecodingError::CannotDecodeFragmentTag)?; + bytes.read_exact(&mut u8_buf)?; if u8_buf[0] != 11 { return Err(DecodingError::InvalidFragmentTag(u8_buf[0])); } - bytes - .read_exact(&mut u256_buf) - .map_err(|_| DecodingError::CannotDecodeVotePlanId)?; + bytes.read_exact(&mut u256_buf)?; let vote_plan_id = u256_buf; - bytes - .read_exact(&mut u8_buf) - .map_err(|_| DecodingError::CannotDecodeProposalIndex)?; + bytes.read_exact(&mut u8_buf)?; let proposal_index = u8_buf[0]; - bytes - .read_exact(&mut u8_buf) - .map_err(|_| DecodingError::CannotDecodeVoteTag)?; + bytes.read_exact(&mut u8_buf)?; let vote = match u8_buf[0] { 1 => { - bytes - .read_exact(&mut u8_buf) - .map_err(|_| DecodingError::CannotDecodePublicVote)?; + bytes.read_exact(&mut u8_buf)?; Vote::Public(u8_buf[0]) }, 2 => { - bytes - .read_exact(&mut u8_buf) - .map_err(|_| DecodingError::CannotDecodeEncryptedVoteSize)?; - let encrypted_vote = EncryptedVote::from_bytes(bytes, u8_buf[0].into()) + bytes.read_exact(&mut u8_buf)?; + let vote = EncryptedVote::from_bytes(bytes, u8_buf[0].into()) .ok_or(DecodingError::CannotDecodeEncryptedVote)?; + bytes = &bytes[vote.bytes_size()..]; - Vote::Private(encrypted_vote) + bytes.read_exact(&mut u8_buf)?; + let proof = VoterProof::from_bytes(bytes, u8_buf[0].into()) + .ok_or(DecodingError::CannotDecodeVoterProof)?; + bytes = &bytes[vote.bytes_size()..]; + + Vote::Private(vote, proof) }, tag => return Err(DecodingError::InvalidVoteTag(tag)), }; + // skip block date (epoch and slot) + bytes.read_exact(&mut u64_buf)?; + + bytes.read_exact(&mut u8_buf)?; + if u8_buf[0] != 1 { + return Err(DecodingError::InvalidNumberOfInputs(u8_buf[0])); + } + bytes.read_exact(&mut u8_buf)?; + if u8_buf[0] != 0 { + return Err(DecodingError::InvalidNumberOfOutputs(u8_buf[0])); + } + + bytes.read_exact(&mut u8_buf)?; + if u8_buf[0] != 0xFF { + return Err(DecodingError::InvalidInputTag(u8_buf[0])); + } + + // skip value + bytes.read_exact(&mut u64_buf)?; + Ok(Self { vote_plan_id, proposal_index, diff --git a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs index f9550ba6aa4..d668e930373 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs @@ -1,7 +1,7 @@ //! Voter proof generation and verification procedures. //! It allows to transparently verify the correctness voter generation and encryption. -use std::ops::{Deref, Mul}; +use std::ops::Mul; use rand_core::CryptoRngCore; @@ -16,13 +16,26 @@ use crate::{ /// Tally proof struct. #[allow(clippy::module_name_repetitions)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct VoterProof(UnitVectorProof); -impl Deref for VoterProof { - type Target = UnitVectorProof; +impl VoterProof { + /// Decode `VoterProof` from bytes. + #[must_use] + pub fn from_bytes(bytes: &[u8], size: usize) -> Option { + UnitVectorProof::from_bytes(bytes, size).map(Self) + } + + /// Get a deserialized bytes size + #[must_use] + pub fn bytes_size(&self) -> usize { + self.0.bytes_size() + } - fn deref(&self) -> &Self::Target { - &self.0 + /// Encode `EncryptedVote` tos bytes. + #[must_use] + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() } } @@ -83,3 +96,19 @@ pub fn verify_voter_proof( ) -> bool { verify_unit_vector_proof(&proof.0, encrypted_vote.0, public_key, &commitment.0) } + +#[cfg(test)] +mod tests { + use proptest::prelude::{any_with, Arbitrary, BoxedStrategy, Strategy}; + + use super::*; + + impl Arbitrary for VoterProof { + type Parameters = usize; + type Strategy = BoxedStrategy; + + fn arbitrary_with(size: Self::Parameters) -> Self::Strategy { + any_with::(size).prop_map(Self).boxed() + } + } +} From 8ad0075b8654dc081fb8e4b5e712d4f69db0461a Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 10 Oct 2024 10:04:39 +0300 Subject: [PATCH 09/44] replace thiserror with anyhow --- rust/catalyst-voting/Cargo.toml | 2 +- .../src/crypto/babystep_giantstep.rs | 36 ++++------ rust/catalyst-voting/src/crypto/elgamal.rs | 14 ++-- .../src/crypto/group/ristretto255.rs | 22 ++++-- .../src/crypto/zk_unit_vector/mod.rs | 37 ++++++---- .../randomness_announcements.rs | 29 +++++--- rust/catalyst-voting/src/txs/mod.rs | 2 +- .../src/vote_protocol/tally/mod.rs | 69 +++++++------------ .../src/vote_protocol/voter/mod.rs | 35 ++++------ .../src/vote_protocol/voter/proof.rs | 38 +++++----- 10 files changed, 142 insertions(+), 142 deletions(-) diff --git a/rust/catalyst-voting/Cargo.toml b/rust/catalyst-voting/Cargo.toml index 866437d4e2b..df6e2aa051c 100644 --- a/rust/catalyst-voting/Cargo.toml +++ b/rust/catalyst-voting/Cargo.toml @@ -11,7 +11,7 @@ license.workspace = true workspace = true [dependencies] -thiserror = "1.0.64" +anyhow = "1.0.89" rand_core = "0.6.4" curve25519-dalek = { version = "4.1.3", features = ["digest"] } blake2b_simd = "1.0.2" diff --git a/rust/catalyst-voting/src/crypto/babystep_giantstep.rs b/rust/catalyst-voting/src/crypto/babystep_giantstep.rs index a5fceb2febc..89b8436faef 100644 --- a/rust/catalyst-voting/src/crypto/babystep_giantstep.rs +++ b/rust/catalyst-voting/src/crypto/babystep_giantstep.rs @@ -3,6 +3,8 @@ use std::collections::HashMap; +use anyhow::{bail, ensure}; + use crate::crypto::group::{GroupElement, Scalar}; /// Default balance value. @@ -22,16 +24,6 @@ pub struct BabyStepGiantStep { giant_step: GroupElement, } -#[derive(thiserror::Error, Debug)] -pub enum BabyStepError { - /// Invalid max value or balance - #[error("Maximum value and balance must be greater than zero, provided max value: {0} and balance: {1}.")] - InvalidMaxValueOrBalance(u64, u64), - /// Max value exceeded - #[error("Max log value exceeded. Means that the actual discrete log for the provided group element is higher than the provided `max_log_value`.")] - MaxLogExceeded, -} - impl BabyStepGiantStep { /// Creates a new setup for the baby-step giant-step algorithm. /// @@ -47,16 +39,15 @@ impl BabyStepGiantStep { /// `baby_step_giant_step` function for the same `max_value`. /// /// # Errors - /// - `BabyStepError` - pub fn new(max_log_value: u64, balance: Option) -> Result { + /// - Maximum value and balance must be greater than zero. + pub fn new(max_log_value: u64, balance: Option) -> anyhow::Result { let balance = balance.unwrap_or(DEFAULT_BALANCE); - if balance == 0 || max_log_value == 0 { - return Err(BabyStepError::InvalidMaxValueOrBalance( - max_log_value, - balance, - )); - } + ensure!( + balance != 0 && max_log_value != 0, + "Maximum value and balance must be greater than zero, + provided max value: {max_log_value} and balance: {balance}." + ); #[allow( clippy::cast_possible_truncation, @@ -85,8 +76,8 @@ impl BabyStepGiantStep { /// Solve the discrete log using baby step giant step algorithm. /// /// # Errors - /// - `BabyStepError` - pub fn discrete_log(&self, mut point: GroupElement) -> Result { + /// - Max log value exceeded. + pub fn discrete_log(&self, mut point: GroupElement) -> anyhow::Result { for baby_step in 0..=self.baby_step_size { if let Some(x) = self.table.get(&point) { let r = baby_step * self.baby_step_size + x; @@ -94,9 +85,12 @@ impl BabyStepGiantStep { } point = &point + &self.giant_step; } + // If we get here, the point is not in the table // So we exceeded the maximum value of the discrete log - Err(BabyStepError::MaxLogExceeded) + bail!("Max log value exceeded. + Means that the actual discrete log for the provided group element is higher than the provided `max_log_value`." + ) } } diff --git a/rust/catalyst-voting/src/crypto/elgamal.rs b/rust/catalyst-voting/src/crypto/elgamal.rs index 3852e25d4ec..f4e05ec67e8 100644 --- a/rust/catalyst-voting/src/crypto/elgamal.rs +++ b/rust/catalyst-voting/src/crypto/elgamal.rs @@ -3,6 +3,7 @@ use std::ops::{Add, Deref, Mul}; +use anyhow::anyhow; use rand_core::CryptoRngCore; use crate::crypto::group::{GroupElement, Scalar}; @@ -77,11 +78,16 @@ impl Ciphertext { } /// Attempt to construct a `Scalar` from a compressed value byte representation. + /// + /// # Errors + /// - Cannot decode group element field. #[allow(clippy::unwrap_used)] - pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> Option { - Some(Self( - GroupElement::from_bytes(bytes[0..32].try_into().unwrap())?, - GroupElement::from_bytes(bytes[32..64].try_into().unwrap())?, + pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result { + Ok(Self( + GroupElement::from_bytes(bytes[0..32].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode first group element field."))?, + GroupElement::from_bytes(bytes[32..64].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode second group element field."))?, )) } } diff --git a/rust/catalyst-voting/src/crypto/group/ristretto255.rs b/rust/catalyst-voting/src/crypto/group/ristretto255.rs index c65924efee5..90bb8853cd5 100644 --- a/rust/catalyst-voting/src/crypto/group/ristretto255.rs +++ b/rust/catalyst-voting/src/crypto/group/ristretto255.rs @@ -7,6 +7,7 @@ use std::{ ops::{Add, Mul, Sub}, }; +use anyhow::anyhow; use curve25519_dalek::{ constants::{RISTRETTO_BASEPOINT_POINT, RISTRETTO_BASEPOINT_TABLE}, digest::{consts::U64, Digest}, @@ -78,8 +79,14 @@ impl Scalar { } /// Attempt to construct a `Scalar` from a canonical byte representation. - pub fn from_bytes(bytes: [u8; Self::BYTES_SIZE]) -> Option { - IScalar::from_canonical_bytes(bytes).map(Scalar).into() + /// + /// # Errors + /// - Cannot decode scalar. + pub fn from_bytes(bytes: [u8; Self::BYTES_SIZE]) -> anyhow::Result { + IScalar::from_canonical_bytes(bytes) + .map(Scalar) + .into_option() + .ok_or(anyhow!("Cannot decode scalar.")) } /// Generate a `Scalar` from a hash digest. @@ -107,9 +114,14 @@ impl GroupElement { } /// Attempt to construct a `Scalar` from a compressed value byte representation. - pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> Option { - Some(GroupElement( - CompressedRistretto::from_slice(bytes).ok()?.decompress()?, + /// + /// # Errors + /// - Cannot decode group element. + pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result { + Ok(GroupElement( + CompressedRistretto::from_slice(bytes)? + .decompress() + .ok_or(anyhow!("Cannot decode group element."))?, )) } } 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 aded9035f04..c5a45b0cc6b 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs @@ -13,6 +13,7 @@ mod utils; use std::{io::Read, ops::Mul}; +use anyhow::anyhow; use challenges::{calculate_first_challenge_hash, calculate_second_challenge_hash}; use polynomial::{calculate_polynomial_val, generate_polynomial, Polynomial}; use rand_core::CryptoRngCore; @@ -35,34 +36,44 @@ pub struct UnitVectorProof( impl UnitVectorProof { /// Decode `UnitVectorProof` from bytes. - pub fn from_bytes(mut bytes: &[u8], size: usize) -> Option { + /// + /// # Errors + /// - Cannot decode announcement value. + /// - Cannot decode ciphertext value. + /// - Cannot decode response randomness value. + /// - Cannot decode scalar value. + pub fn from_bytes(mut bytes: &[u8], size: usize) -> anyhow::Result { let mut ann_buf = [0u8; Announcement::BYTES_SIZE]; let mut dl_buf = [0u8; Ciphertext::BYTES_SIZE]; let mut rr_buf = [0u8; ResponseRandomness::BYTES_SIZE]; let ann = (0..size) - .map(|_| { - bytes.read_exact(&mut ann_buf).ok()?; + .map(|i| { + bytes.read_exact(&mut ann_buf)?; Announcement::from_bytes(&ann_buf) + .map_err(|e| anyhow!("Cannot decode announcement at {i}, error: {e}.")) }) - .collect::>()?; + .collect::>()?; let dl = (0..size) - .map(|_| { - bytes.read_exact(&mut dl_buf).ok()?; + .map(|i| { + bytes.read_exact(&mut dl_buf)?; Ciphertext::from_bytes(&dl_buf) + .map_err(|e| anyhow!("Cannot decode ciphertext at {i}, error: {e}.")) }) - .collect::>()?; + .collect::>()?; let rr = (0..size) - .map(|_| { - bytes.read_exact(&mut rr_buf).ok()?; + .map(|i| { + bytes.read_exact(&mut rr_buf)?; ResponseRandomness::from_bytes(&rr_buf) + .map_err(|e| anyhow!("Cannot decode response randomness at {i}, error: {e}.")) }) - .collect::>()?; + .collect::>()?; let mut scalar_buf = [0u8; Scalar::BYTES_SIZE]; - bytes.read_exact(&mut scalar_buf).ok()?; - let scalar = Scalar::from_bytes(scalar_buf)?; - Some(Self(ann, dl, rr, scalar)) + bytes.read_exact(&mut scalar_buf)?; + let scalar = + Scalar::from_bytes(scalar_buf).map_err(|_| anyhow!("Cannot decode scalar field."))?; + Ok(Self(ann, dl, rr, scalar)) } /// Get a deserialized bytes size diff --git a/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs b/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs index 460b87205f2..5610eb80c30 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs @@ -4,6 +4,7 @@ use std::ops::Mul; +use anyhow::anyhow; use rand_core::CryptoRngCore; use crate::crypto::group::{GroupElement, Scalar}; @@ -63,11 +64,14 @@ impl Announcement { /// # Errors /// - `AnnouncementDecodingError` #[allow(clippy::unwrap_used)] - pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> Option { - let i = GroupElement::from_bytes(bytes[0..32].try_into().unwrap())?; - let b = GroupElement::from_bytes(bytes[32..64].try_into().unwrap())?; - let a = GroupElement::from_bytes(bytes[64..96].try_into().unwrap())?; - Some(Self { i, b, a }) + pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result { + let i = GroupElement::from_bytes(bytes[0..32].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode `i` group element field."))?; + let b = GroupElement::from_bytes(bytes[32..64].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode `b` group element field."))?; + let a = GroupElement::from_bytes(bytes[64..96].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode `a` group element field."))?; + Ok(Self { i, b, a }) } /// Encode `Announcement` tos bytes. @@ -108,13 +112,16 @@ impl ResponseRandomness { /// Decode `ResponseRandomness` from bytes. /// /// # Errors - /// - `ResponseRandomnessDecodingError` + /// - Cannot decode scalar field. #[allow(clippy::unwrap_used)] - pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> Option { - let z = Scalar::from_bytes(bytes[0..32].try_into().unwrap())?; - let w = Scalar::from_bytes(bytes[32..64].try_into().unwrap())?; - let v = Scalar::from_bytes(bytes[64..96].try_into().unwrap())?; - Some(Self { z, w, v }) + pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result { + let z = Scalar::from_bytes(bytes[0..32].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode `z` scalar field."))?; + let w = Scalar::from_bytes(bytes[32..64].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode `w` scalar field."))?; + let v = Scalar::from_bytes(bytes[64..96].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode `v` scalar field."))?; + Ok(Self { z, w, v }) } /// Encode `ResponseRandomness` tos bytes. diff --git a/rust/catalyst-voting/src/txs/mod.rs b/rust/catalyst-voting/src/txs/mod.rs index ed61e561416..c430e0d2941 100644 --- a/rust/catalyst-voting/src/txs/mod.rs +++ b/rust/catalyst-voting/src/txs/mod.rs @@ -1,4 +1,4 @@ //! A catalyst transaction objects implementation mod utils; -pub mod v1; +// pub mod v1; diff --git a/rust/catalyst-voting/src/vote_protocol/tally/mod.rs b/rust/catalyst-voting/src/vote_protocol/tally/mod.rs index ee63f76ae31..7e29cc2caef 100644 --- a/rust/catalyst-voting/src/vote_protocol/tally/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/tally/mod.rs @@ -4,6 +4,8 @@ pub mod proof; use std::ops::{Add, Mul}; +use anyhow::{anyhow, ensure}; + use crate::{ crypto::{ babystep_giantstep::BabyStepGiantStep, @@ -24,14 +26,6 @@ pub struct DecryptionTallySetup { #[allow(clippy::module_name_repetitions)] pub struct EncryptedTally(Ciphertext); -/// Tally error -#[derive(thiserror::Error, Debug)] -pub enum DecryptionTallySetupError { - /// Votes and voting power mismatch - #[error("Total voting power must more than 0.")] - InvalidTotalVotingPowerAmount, -} - impl DecryptionTallySetup { /// Generate a decryption tally setup. /// `total_voting_power` must be a total sum of all voting powers used in the `tally` @@ -41,46 +35,38 @@ impl DecryptionTallySetup { /// `decrypt_tally` function for the same `voting_powers`. /// /// # Errors - /// - `DecryptionTallySetupError` - pub fn new(total_voting_power: u64) -> Result { - let discrete_log_setup = BabyStepGiantStep::new(total_voting_power, None) - .map_err(|_| DecryptionTallySetupError::InvalidTotalVotingPowerAmount)?; + /// - Total voting power must more than 0. + pub fn new(total_voting_power: u64) -> anyhow::Result { + let discrete_log_setup = + BabyStepGiantStep::new(total_voting_power, None).map_err(|_| { + anyhow!("Total voting power must more than 0, provided: {total_voting_power}") + })?; Ok(Self { discrete_log_setup }) } } -/// Tally error -#[derive(thiserror::Error, Debug)] -#[allow(clippy::module_name_repetitions)] -pub enum TallyError { - /// Votes and voting power mismatch - #[error("Votes and voting power mismatch. Votes amount: {0}. Voting powers amount: {1}.")] - VotingPowerAndVotesMismatch(usize, usize), - /// Invalid encrypted vote - #[error("Invalid encrypted vote at index {0}. Does not have a ciphertext for the voting option {1}.")] - InvalidEncryptedVote(usize, usize), -} - /// Tally function. /// More detailed described [here](https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/voting_transaction/crypto/#homomorphic-tally) /// /// # Errors -/// - `TallyError` +/// - Votes and voting power length mismatch. +/// - Invalid encrypted vote at index `i`. Does not have a ciphertext for the voting +/// option `voting_option`. pub fn tally( voting_option: usize, votes: &[EncryptedVote], voting_powers: &[u64], -) -> Result { - if votes.len() != voting_powers.len() { - return Err(TallyError::VotingPowerAndVotesMismatch( - votes.len(), - voting_powers.len(), - )); - } +) -> anyhow::Result { + ensure!( + votes.len() == voting_powers.len(), + "Votes and voting power length mismatch. Votes amount: {0}. Voting powers amount: {1}.", + votes.len(), + voting_powers.len(), + ); let mut ciphertexts_per_voting_option = Vec::new(); for (i, vote) in votes.iter().enumerate() { let ciphertext = vote .get_ciphertext_for_choice(voting_option) - .ok_or(TallyError::InvalidEncryptedVote(i, voting_option))?; + .ok_or(anyhow!("Invalid encrypted vote at index {i}. Does not have a ciphertext for the voting option {voting_option}.") )?; ciphertexts_per_voting_option.push(ciphertext); } @@ -98,30 +84,21 @@ pub fn tally( Ok(EncryptedTally(res)) } -/// Tally error -#[derive(thiserror::Error, Debug)] -pub enum DecryptTallyError { - /// Cannot decrypt tally result - #[error( - "Cannot decrypt tally result. Provided an invalid secret key or invalid encrypted tally result." - )] - CannotDecryptTallyResult, -} - /// Decrypts the encrypted tally result. /// More detailed described [here](https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/voting_transaction/crypto/#tally-decryption) /// /// # Errors -/// - `DecryptTallyError` +/// - Cannot decrypt tally result. Provided an invalid secret key or invalid encrypted +/// tally result. #[allow(clippy::module_name_repetitions)] pub fn decrypt_tally( tally_result: &EncryptedTally, secret_key: &SecretKey, setup: &DecryptionTallySetup, -) -> Result { +) -> anyhow::Result { let ge = decrypt(&tally_result.0, secret_key); let res = setup .discrete_log_setup .discrete_log(ge) - .map_err(|_| DecryptTallyError::CannotDecryptTallyResult)?; + .map_err(|_| anyhow!("Cannot decrypt tally result. Provided an invalid secret key or invalid encrypted tally result."))?; Ok(res) } diff --git a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index 1f377bc7ff6..0f9a1973287 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -4,6 +4,7 @@ pub mod proof; use std::io::Read; +use anyhow::{anyhow, ensure}; use rand_core::CryptoRngCore; use crate::crypto::{ @@ -40,18 +41,21 @@ impl EncryptionRandomness { impl EncryptedVote { /// Decode `EncryptedVote` from bytes. - #[must_use] - pub fn from_bytes(mut bytes: &[u8], size: usize) -> Option { + /// + /// # Errors + /// - Cannot decode ciphertext. + pub fn from_bytes(mut bytes: &[u8], size: usize) -> anyhow::Result { let mut ciph_buf = [0u8; Ciphertext::BYTES_SIZE]; let ciphertexts = (0..size) - .map(|_| { - bytes.read_exact(&mut ciph_buf).ok()?; + .map(|i| { + bytes.read_exact(&mut ciph_buf)?; Ciphertext::from_bytes(&ciph_buf) + .map_err(|e| anyhow!("Cannot decode ciphertext at {i}, error: {e}")) }) - .collect::>()?; + .collect::>()?; - Some(Self(ciphertexts)) + Ok(Self(ciphertexts)) } /// Get a deserialized bytes size @@ -76,26 +80,15 @@ impl EncryptedVote { } } -/// Encrypted vote error -#[derive(thiserror::Error, Debug)] -pub enum VoteError { - /// Incorrect voting choice - #[error( - "Invalid voting choice, the value of choice: {0}, should be less than the number of voting options: {1}." - )] - IncorrectChoiceError(usize, usize), -} - impl Vote { /// Generate a vote. /// More detailed described [here](https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/voting_transaction/crypto/#voting-choice) /// /// # Errors - /// - `VoteError` - pub fn new(choice: usize, voting_options: usize) -> Result { - if choice >= voting_options { - return Err(VoteError::IncorrectChoiceError(choice, voting_options)); - } + /// - Invalid voting choice, the value of `choice`, should be less than the number + /// of `voting_options`. + pub fn new(choice: usize, voting_options: usize) -> anyhow::Result { + ensure!(choice < voting_options,"Invalid voting choice, the value of choice: {choice}, should be less than the number of voting options: {voting_options}." ); Ok(Vote { choice, diff --git a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs index d668e930373..6855420ffae 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs @@ -3,6 +3,7 @@ use std::ops::Mul; +use anyhow::ensure; use rand_core::CryptoRngCore; use super::{EncryptedVote, EncryptionRandomness, Vote}; @@ -21,8 +22,13 @@ pub struct VoterProof(UnitVectorProof); impl VoterProof { /// Decode `VoterProof` from bytes. - #[must_use] - pub fn from_bytes(bytes: &[u8], size: usize) -> Option { + /// + /// # Errors + /// - Cannot decode announcement value. + /// - Cannot decode ciphertext value. + /// - Cannot decode response randomness value. + /// - Cannot decode scalar value. + pub fn from_bytes(bytes: &[u8], size: usize) -> anyhow::Result { UnitVectorProof::from_bytes(bytes, size).map(Self) } @@ -49,31 +55,25 @@ impl VoterProofCommitment { } } -/// Generate voter proof error -#[derive(thiserror::Error, Debug)] -pub enum GenerateVoterProofError { - /// Arguments mismatch - #[error("Provided arguments mismatch. Size of the provided `vote`: {0}, `encrypted_vote: {1}` and `randomness`: {2} must be equal with each other.")] - ArgumentsMismatch(usize, usize, usize), -} - /// Generates a voter proof. /// More detailed described [here](https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/voting_transaction/crypto/#voters-proof) /// /// # Errors -/// - `GenerateVoterProofError` +/// - 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( vote: &Vote, encrypted_vote: EncryptedVote, randomness: EncryptionRandomness, public_key: &PublicKey, commitment: &VoterProofCommitment, rng: &mut R, -) -> Result { - if vote.voting_options != encrypted_vote.0.len() || vote.voting_options != randomness.0.len() { - return Err(GenerateVoterProofError::ArgumentsMismatch( - vote.voting_options, - encrypted_vote.0.len(), - randomness.0.len(), - )); - } +) -> anyhow::Result { + ensure!( + vote.voting_options == encrypted_vote.0.len() && vote.voting_options == randomness.0.len(), + "Provided arguments mismatch. + Size of the provided `vote`: {0}, `encrypted_vote: {1}` and `randomness`: {2} must be equal with each other.", + vote.voting_options, + encrypted_vote.0.len(), + randomness.0.len(), + ); let proof = generate_unit_vector_proof( &vote.to_unit_vector(), From 8f652bccf3cad6148e6e8ee58732dc06e9c9495b Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 10 Oct 2024 11:02:04 +0300 Subject: [PATCH 10/44] move decoding functionalities to separate module --- .../src/crypto/elgamal/decoding.rs | 46 +++++ .../src/crypto/{elgamal.rs => elgamal/mod.rs} | 35 +--- .../src/crypto/group/ristretto255/decoding.rs | 71 ++++++++ .../{ristretto255.rs => ristretto255/mod.rs} | 58 +------ .../src/crypto/zk_unit_vector/decoding.rs | 163 ++++++++++++++++++ .../src/crypto/zk_unit_vector/mod.rs | 79 +-------- .../randomness_announcements.rs | 72 -------- .../src/vote_protocol/voter/decoding.rs | 88 ++++++++++ .../src/vote_protocol/voter/mod.rs | 53 +----- .../src/vote_protocol/voter/proof.rs | 27 +-- 10 files changed, 378 insertions(+), 314 deletions(-) create mode 100644 rust/catalyst-voting/src/crypto/elgamal/decoding.rs rename rust/catalyst-voting/src/crypto/{elgamal.rs => elgamal/mod.rs} (78%) create mode 100644 rust/catalyst-voting/src/crypto/group/ristretto255/decoding.rs rename rust/catalyst-voting/src/crypto/group/{ristretto255.rs => ristretto255/mod.rs} (74%) create mode 100644 rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs create mode 100644 rust/catalyst-voting/src/vote_protocol/voter/decoding.rs diff --git a/rust/catalyst-voting/src/crypto/elgamal/decoding.rs b/rust/catalyst-voting/src/crypto/elgamal/decoding.rs new file mode 100644 index 00000000000..b9f094d85b9 --- /dev/null +++ b/rust/catalyst-voting/src/crypto/elgamal/decoding.rs @@ -0,0 +1,46 @@ +//! Elgamal objects decoding implementation + +use anyhow::anyhow; + +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. + 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()); + res[32..64].copy_from_slice(&self.1.to_bytes()); + res + } + + /// Attempt to construct a `Scalar` from a compressed value byte representation. + /// + /// # Errors + /// - Cannot decode group element field. + #[allow(clippy::unwrap_used)] + pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result { + Ok(Self( + GroupElement::from_bytes(bytes[0..32].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode first group element field."))?, + GroupElement::from_bytes(bytes[32..64].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode second group element field."))?, + )) + } +} + +#[cfg(test)] +mod tests { + use test_strategy::proptest; + + use super::*; + + #[proptest] + fn ciphertext_to_bytes_from_bytes_test(c1: Ciphertext) { + let bytes = c1.to_bytes(); + let c2 = Ciphertext::from_bytes(&bytes).unwrap(); + assert_eq!(c1, c2); + } +} diff --git a/rust/catalyst-voting/src/crypto/elgamal.rs b/rust/catalyst-voting/src/crypto/elgamal/mod.rs similarity index 78% rename from rust/catalyst-voting/src/crypto/elgamal.rs rename to rust/catalyst-voting/src/crypto/elgamal/mod.rs index f4e05ec67e8..aefed68f660 100644 --- a/rust/catalyst-voting/src/crypto/elgamal.rs +++ b/rust/catalyst-voting/src/crypto/elgamal/mod.rs @@ -1,9 +1,10 @@ //! 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 anyhow::anyhow; use rand_core::CryptoRngCore; use crate::crypto::group::{GroupElement, Scalar}; @@ -50,9 +51,6 @@ impl SecretKey { } impl Ciphertext { - /// `Ciphertext` bytes size - pub const BYTES_SIZE: usize = GroupElement::BYTES_SIZE * 2; - /// Generate a zero `Ciphertext`. /// The same as encrypt a `Scalar::zero()` message and `Scalar::zero()` randomness. pub fn zero() -> Self { @@ -68,28 +66,6 @@ impl Ciphertext { pub fn second(&self) -> &GroupElement { &self.1 } - - /// Convert this `Ciphertext` to its underlying sequence of bytes. - 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()); - res[32..64].copy_from_slice(&self.1.to_bytes()); - res - } - - /// Attempt to construct a `Scalar` from a compressed value byte representation. - /// - /// # Errors - /// - Cannot decode group element field. - #[allow(clippy::unwrap_used)] - pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result { - Ok(Self( - GroupElement::from_bytes(bytes[0..32].try_into().unwrap()) - .map_err(|_| anyhow!("Cannot decode first group element field."))?, - GroupElement::from_bytes(bytes[32..64].try_into().unwrap()) - .map_err(|_| anyhow!("Cannot decode second group element field."))?, - )) - } } /// Given a `message` represented as a `Scalar`, return a ciphertext using the @@ -153,13 +129,6 @@ mod tests { } } - #[proptest] - fn ciphertext_to_bytes_from_bytes_test(c1: Ciphertext) { - let bytes = c1.to_bytes(); - let c2 = Ciphertext::from_bytes(&bytes).unwrap(); - assert_eq!(c1, c2); - } - #[proptest] fn ciphertext_add_test(e1: Scalar, e2: Scalar, e3: Scalar, e4: Scalar) { let g1 = GroupElement::GENERATOR.mul(&e1); diff --git a/rust/catalyst-voting/src/crypto/group/ristretto255/decoding.rs b/rust/catalyst-voting/src/crypto/group/ristretto255/decoding.rs new file mode 100644 index 00000000000..4076ee9b092 --- /dev/null +++ b/rust/catalyst-voting/src/crypto/group/ristretto255/decoding.rs @@ -0,0 +1,71 @@ +//! ristretto255 objects decoding implementation + +use anyhow::anyhow; +use curve25519_dalek::{ristretto::CompressedRistretto, scalar::Scalar as IScalar}; + +use super::{GroupElement, Scalar}; + +impl Scalar { + /// `Scalar` bytes size + pub const BYTES_SIZE: usize = 32; + + /// Attempt to construct a `Scalar` from a canonical byte representation. + /// + /// # Errors + /// - Cannot decode scalar. + pub fn from_bytes(bytes: [u8; Self::BYTES_SIZE]) -> anyhow::Result { + IScalar::from_canonical_bytes(bytes) + .map(Scalar) + .into_option() + .ok_or(anyhow!("Cannot decode scalar.")) + } + + /// Convert this `Scalar` to its underlying sequence of bytes. + pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { + self.0.to_bytes() + } +} + +impl GroupElement { + /// `Scalar` bytes size + pub const BYTES_SIZE: usize = 32; + + /// Attempt to construct a `Scalar` from a compressed value byte representation. + /// + /// # Errors + /// - Cannot decode group element. + pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result { + Ok(GroupElement( + CompressedRistretto::from_slice(bytes)? + .decompress() + .ok_or(anyhow!("Cannot decode group element."))?, + )) + } + + /// Convert this `GroupElement` to its underlying sequence of bytes. + /// Always encode the compressed value. + pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { + self.0.compress().to_bytes() + } +} + +#[cfg(test)] +mod tests { + use test_strategy::proptest; + + use super::*; + + #[proptest] + fn scalar_to_bytes_from_bytes_test(e1: Scalar) { + let bytes = e1.to_bytes(); + let e2 = Scalar::from_bytes(bytes).unwrap(); + assert_eq!(e1, e2); + } + + #[proptest] + fn group_element_to_bytes_from_bytes_test(ge1: GroupElement) { + let bytes = ge1.to_bytes(); + let ge2 = GroupElement::from_bytes(&bytes).unwrap(); + assert_eq!(ge1, ge2); + } +} diff --git a/rust/catalyst-voting/src/crypto/group/ristretto255.rs b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs similarity index 74% rename from rust/catalyst-voting/src/crypto/group/ristretto255.rs rename to rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs index 90bb8853cd5..b636f8a8648 100644 --- a/rust/catalyst-voting/src/crypto/group/ristretto255.rs +++ b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs @@ -2,16 +2,17 @@ // cspell: words BASEPOINT +mod decoding; + use std::{ hash::Hash, ops::{Add, Mul, Sub}, }; -use anyhow::anyhow; use curve25519_dalek::{ constants::{RISTRETTO_BASEPOINT_POINT, RISTRETTO_BASEPOINT_TABLE}, digest::{consts::U64, Digest}, - ristretto::{CompressedRistretto, RistrettoPoint as Point}, + ristretto::RistrettoPoint as Point, scalar::Scalar as IScalar, traits::Identity, }; @@ -38,9 +39,6 @@ impl Hash for GroupElement { } impl Scalar { - /// `Scalar` bytes size - pub const BYTES_SIZE: usize = 32; - /// Generate a random scalar value from the random number generator. pub fn random(rng: &mut R) -> Self { let mut scalar_bytes = [0u8; 64]; @@ -73,22 +71,6 @@ impl Scalar { Scalar(self.0.invert()) } - /// Convert this `Scalar` to its underlying sequence of bytes. - pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { - self.0.to_bytes() - } - - /// Attempt to construct a `Scalar` from a canonical byte representation. - /// - /// # Errors - /// - Cannot decode scalar. - pub fn from_bytes(bytes: [u8; Self::BYTES_SIZE]) -> anyhow::Result { - IScalar::from_canonical_bytes(bytes) - .map(Scalar) - .into_option() - .ok_or(anyhow!("Cannot decode scalar.")) - } - /// Generate a `Scalar` from a hash digest. pub fn from_hash(hash: D) -> Scalar where D: Digest { @@ -97,8 +79,6 @@ impl Scalar { } impl GroupElement { - /// `GroupElement` bytes size - pub const BYTES_SIZE: usize = 32; /// ristretto255 group generator. pub const GENERATOR: GroupElement = GroupElement(RISTRETTO_BASEPOINT_POINT); @@ -106,24 +86,6 @@ impl GroupElement { pub fn zero() -> Self { GroupElement(Point::identity()) } - - /// Convert this `GroupElement` to its underlying sequence of bytes. - /// Always encode the compressed value. - pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { - self.0.compress().to_bytes() - } - - /// Attempt to construct a `Scalar` from a compressed value byte representation. - /// - /// # Errors - /// - Cannot decode group element. - pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result { - Ok(GroupElement( - CompressedRistretto::from_slice(bytes)? - .decompress() - .ok_or(anyhow!("Cannot decode group element."))?, - )) - } } // `std::ops` traits implementations @@ -218,20 +180,6 @@ mod tests { } } - #[proptest] - fn scalar_to_bytes_from_bytes_test(e1: Scalar) { - let bytes = e1.to_bytes(); - let e2 = Scalar::from_bytes(bytes).unwrap(); - assert_eq!(e1, e2); - } - - #[proptest] - fn group_element_to_bytes_from_bytes_test(ge1: GroupElement) { - let bytes = ge1.to_bytes(); - let ge2 = GroupElement::from_bytes(&bytes).unwrap(); - assert_eq!(ge1, ge2); - } - #[proptest] fn scalar_arithmetic_tests(e1: Scalar, e2: Scalar, e3: Scalar) { assert_eq!(&(&e1 + &e2) + &e3, &e1 + &(&e2 + &e3)); diff --git a/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs b/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs new file mode 100644 index 00000000000..3554e03e3aa --- /dev/null +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs @@ -0,0 +1,163 @@ +//! ZK Unit Vector objects decoding implementation + +use std::io::Read; + +use anyhow::anyhow; + +use super::{Announcement, Ciphertext, GroupElement, ResponseRandomness, Scalar, UnitVectorProof}; + +impl UnitVectorProof { + /// Decode `UnitVectorProof` from bytes. + /// + /// # Errors + /// - Cannot decode announcement value. + /// - Cannot decode ciphertext value. + /// - Cannot decode response randomness value. + /// - Cannot decode scalar value. + pub fn from_bytes(mut bytes: &[u8], size: usize) -> anyhow::Result { + let mut ann_buf = [0u8; Announcement::BYTES_SIZE]; + let mut dl_buf = [0u8; Ciphertext::BYTES_SIZE]; + let mut rr_buf = [0u8; ResponseRandomness::BYTES_SIZE]; + + let ann = (0..size) + .map(|i| { + bytes.read_exact(&mut ann_buf)?; + Announcement::from_bytes(&ann_buf) + .map_err(|e| anyhow!("Cannot decode announcement at {i}, error: {e}.")) + }) + .collect::>()?; + let dl = (0..size) + .map(|i| { + bytes.read_exact(&mut dl_buf)?; + Ciphertext::from_bytes(&dl_buf) + .map_err(|e| anyhow!("Cannot decode ciphertext at {i}, error: {e}.")) + }) + .collect::>()?; + let rr = (0..size) + .map(|i| { + bytes.read_exact(&mut rr_buf)?; + ResponseRandomness::from_bytes(&rr_buf) + .map_err(|e| anyhow!("Cannot decode response randomness at {i}, error: {e}.")) + }) + .collect::>()?; + + let mut scalar_buf = [0u8; Scalar::BYTES_SIZE]; + bytes.read_exact(&mut scalar_buf)?; + let scalar = + Scalar::from_bytes(scalar_buf).map_err(|_| anyhow!("Cannot decode scalar field."))?; + Ok(Self(ann, dl, rr, scalar)) + } + + /// Get a deserialized bytes size + #[must_use] + pub fn bytes_size(&self) -> usize { + self.0.len() * Announcement::BYTES_SIZE + + self.0.len() * Ciphertext::BYTES_SIZE + + self.0.len() * ResponseRandomness::BYTES_SIZE + } + + /// Encode `EncryptedVote` tos bytes. + #[must_use] + pub fn to_bytes(&self) -> Vec { + let mut res = Vec::with_capacity(self.bytes_size()); + self.0 + .iter() + .for_each(|c| res.extend_from_slice(&c.to_bytes())); + self.1 + .iter() + .for_each(|c| res.extend_from_slice(&c.to_bytes())); + self.2 + .iter() + .for_each(|c| res.extend_from_slice(&c.to_bytes())); + res.extend_from_slice(&self.3.to_bytes()); + res + } +} + +impl Announcement { + /// `Announcement` bytes size + pub const BYTES_SIZE: usize = GroupElement::BYTES_SIZE * 3; + + /// Decode `Announcement` from bytes. + /// + /// # Errors + /// - `AnnouncementDecodingError` + #[allow(clippy::unwrap_used)] + pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result { + let i = GroupElement::from_bytes(bytes[0..32].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode `i` group element field."))?; + let b = GroupElement::from_bytes(bytes[32..64].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode `b` group element field."))?; + let a = GroupElement::from_bytes(bytes[64..96].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode `a` group element field."))?; + Ok(Self { i, b, a }) + } + + /// Encode `Announcement` tos bytes. + #[must_use] + pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { + let mut res = [0; 96]; + res[0..32].copy_from_slice(&self.i.to_bytes()); + res[32..64].copy_from_slice(&self.b.to_bytes()); + res[64..96].copy_from_slice(&self.a.to_bytes()); + res + } +} + +impl ResponseRandomness { + /// `ResponseRandomness` bytes size + pub const BYTES_SIZE: usize = Scalar::BYTES_SIZE * 3; + + /// Decode `ResponseRandomness` from bytes. + /// + /// # Errors + /// - Cannot decode scalar field. + #[allow(clippy::unwrap_used)] + pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result { + let z = Scalar::from_bytes(bytes[0..32].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode `z` scalar field."))?; + let w = Scalar::from_bytes(bytes[32..64].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode `w` scalar field."))?; + let v = Scalar::from_bytes(bytes[64..96].try_into().unwrap()) + .map_err(|_| anyhow!("Cannot decode `v` scalar field."))?; + Ok(Self { z, w, v }) + } + + /// Encode `ResponseRandomness` tos bytes. + #[must_use] + pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { + let mut res = [0; 96]; + res[0..32].copy_from_slice(&self.z.to_bytes()); + res[32..64].copy_from_slice(&self.w.to_bytes()); + res[64..96].copy_from_slice(&self.v.to_bytes()); + res + } +} + +#[cfg(test)] +mod tests { + use test_strategy::proptest; + + use super::*; + + #[proptest] + fn proof_to_bytes_from_bytes_test(p1: UnitVectorProof) { + let bytes = p1.to_bytes(); + let p2 = UnitVectorProof::from_bytes(&bytes, p1.0.len()).unwrap(); + assert_eq!(p1, p2); + } + + #[proptest] + fn announcement_to_bytes_from_bytes_test(a1: Announcement) { + let bytes = a1.to_bytes(); + let a2 = Announcement::from_bytes(&bytes).unwrap(); + assert_eq!(a1, a2); + } + + #[proptest] + fn response_randomness_to_bytes_from_bytes_test(r1: ResponseRandomness) { + let bytes = r1.to_bytes(); + let r2 = ResponseRandomness::from_bytes(&bytes).unwrap(); + assert_eq!(r1, r2); + } +} 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 c5a45b0cc6b..968988ef0c4 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs @@ -7,13 +7,13 @@ // cspell: words Zhang, Oliynykov, Balogum mod challenges; +mod decoding; mod polynomial; mod randomness_announcements; mod utils; -use std::{io::Read, ops::Mul}; +use std::ops::Mul; -use anyhow::anyhow; use challenges::{calculate_first_challenge_hash, calculate_second_challenge_hash}; use polynomial::{calculate_polynomial_val, generate_polynomial, Polynomial}; use rand_core::CryptoRngCore; @@ -34,74 +34,6 @@ pub struct UnitVectorProof( Scalar, ); -impl UnitVectorProof { - /// Decode `UnitVectorProof` from bytes. - /// - /// # Errors - /// - Cannot decode announcement value. - /// - Cannot decode ciphertext value. - /// - Cannot decode response randomness value. - /// - Cannot decode scalar value. - pub fn from_bytes(mut bytes: &[u8], size: usize) -> anyhow::Result { - let mut ann_buf = [0u8; Announcement::BYTES_SIZE]; - let mut dl_buf = [0u8; Ciphertext::BYTES_SIZE]; - let mut rr_buf = [0u8; ResponseRandomness::BYTES_SIZE]; - - let ann = (0..size) - .map(|i| { - bytes.read_exact(&mut ann_buf)?; - Announcement::from_bytes(&ann_buf) - .map_err(|e| anyhow!("Cannot decode announcement at {i}, error: {e}.")) - }) - .collect::>()?; - let dl = (0..size) - .map(|i| { - bytes.read_exact(&mut dl_buf)?; - Ciphertext::from_bytes(&dl_buf) - .map_err(|e| anyhow!("Cannot decode ciphertext at {i}, error: {e}.")) - }) - .collect::>()?; - let rr = (0..size) - .map(|i| { - bytes.read_exact(&mut rr_buf)?; - ResponseRandomness::from_bytes(&rr_buf) - .map_err(|e| anyhow!("Cannot decode response randomness at {i}, error: {e}.")) - }) - .collect::>()?; - - let mut scalar_buf = [0u8; Scalar::BYTES_SIZE]; - bytes.read_exact(&mut scalar_buf)?; - let scalar = - Scalar::from_bytes(scalar_buf).map_err(|_| anyhow!("Cannot decode scalar field."))?; - Ok(Self(ann, dl, rr, scalar)) - } - - /// Get a deserialized bytes size - #[must_use] - pub fn bytes_size(&self) -> usize { - self.0.len() * Announcement::BYTES_SIZE - + self.0.len() * Ciphertext::BYTES_SIZE - + self.0.len() * ResponseRandomness::BYTES_SIZE - } - - /// Encode `EncryptedVote` tos bytes. - #[must_use] - pub fn to_bytes(&self) -> Vec { - let mut res = Vec::with_capacity(self.bytes_size()); - self.0 - .iter() - .for_each(|c| res.extend_from_slice(&c.to_bytes())); - self.1 - .iter() - .for_each(|c| res.extend_from_slice(&c.to_bytes())); - self.2 - .iter() - .for_each(|c| res.extend_from_slice(&c.to_bytes())); - res.extend_from_slice(&self.3.to_bytes()); - res - } -} - /// Generates a unit vector proof. /// /// `unit_vector` must be a collection of `Scalar` where only one element is equal to @@ -329,13 +261,6 @@ mod tests { } } - #[proptest] - fn proof_to_bytes_from_bytes_test(p1: UnitVectorProof) { - let bytes = p1.to_bytes(); - let p2 = UnitVectorProof::from_bytes(&bytes, p1.0.len()).unwrap(); - assert_eq!(p1, p2); - } - fn is_unit_vector(vector: &[Scalar]) -> bool { let ones = vector.iter().filter(|s| s == &&Scalar::one()).count(); let zeros = vector.iter().filter(|s| s == &&Scalar::zero()).count(); diff --git a/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs b/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs index 5610eb80c30..4771a04a4a3 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/randomness_announcements.rs @@ -4,7 +4,6 @@ use std::ops::Mul; -use anyhow::anyhow; use rand_core::CryptoRngCore; use crate::crypto::group::{GroupElement, Scalar}; @@ -39,9 +38,6 @@ pub struct Announcement { } impl Announcement { - /// `Announcement` bytes size - pub const BYTES_SIZE: usize = GroupElement::BYTES_SIZE * 3; - pub(crate) fn new( i_bit: bool, rand: &BlindingRandomness, commitment_key: &GroupElement, ) -> Self { @@ -58,31 +54,6 @@ impl Announcement { }; Self { i, b, a } } - - /// Decode `Announcement` from bytes. - /// - /// # Errors - /// - `AnnouncementDecodingError` - #[allow(clippy::unwrap_used)] - pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result { - let i = GroupElement::from_bytes(bytes[0..32].try_into().unwrap()) - .map_err(|_| anyhow!("Cannot decode `i` group element field."))?; - let b = GroupElement::from_bytes(bytes[32..64].try_into().unwrap()) - .map_err(|_| anyhow!("Cannot decode `b` group element field."))?; - let a = GroupElement::from_bytes(bytes[64..96].try_into().unwrap()) - .map_err(|_| anyhow!("Cannot decode `a` group element field."))?; - Ok(Self { i, b, a }) - } - - /// Encode `Announcement` tos bytes. - #[must_use] - pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { - let mut res = [0; 96]; - res[0..32].copy_from_slice(&self.i.to_bytes()); - res[32..64].copy_from_slice(&self.b.to_bytes()); - res[64..96].copy_from_slice(&self.a.to_bytes()); - res - } } /// Response encoding the bits of the private vector, and the randomness of @@ -95,9 +66,6 @@ pub struct ResponseRandomness { } impl ResponseRandomness { - /// `ResponseRandomness` bytes size - pub const BYTES_SIZE: usize = Scalar::BYTES_SIZE * 3; - pub(crate) fn new(i_bit: bool, rand: &BlindingRandomness, com_2: &Scalar) -> Self { let z = if i_bit { com_2 + &rand.betta @@ -108,31 +76,6 @@ impl ResponseRandomness { let v = &(&rand.alpha * &(com_2 - &z)) + &rand.delta; Self { z, w, v } } - - /// Decode `ResponseRandomness` from bytes. - /// - /// # Errors - /// - Cannot decode scalar field. - #[allow(clippy::unwrap_used)] - pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result { - let z = Scalar::from_bytes(bytes[0..32].try_into().unwrap()) - .map_err(|_| anyhow!("Cannot decode `z` scalar field."))?; - let w = Scalar::from_bytes(bytes[32..64].try_into().unwrap()) - .map_err(|_| anyhow!("Cannot decode `w` scalar field."))?; - let v = Scalar::from_bytes(bytes[64..96].try_into().unwrap()) - .map_err(|_| anyhow!("Cannot decode `v` scalar field."))?; - Ok(Self { z, w, v }) - } - - /// Encode `ResponseRandomness` tos bytes. - #[must_use] - pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { - let mut res = [0; 96]; - res[0..32].copy_from_slice(&self.z.to_bytes()); - res[32..64].copy_from_slice(&self.w.to_bytes()); - res[64..96].copy_from_slice(&self.v.to_bytes()); - res - } } #[cfg(test)] @@ -141,7 +84,6 @@ mod tests { arbitrary::any, prelude::{Arbitrary, BoxedStrategy, Strategy}, }; - use test_strategy::proptest; use super::*; @@ -184,18 +126,4 @@ mod tests { .boxed() } } - - #[proptest] - fn announcement_to_bytes_from_bytes_test(a1: Announcement) { - let bytes = a1.to_bytes(); - let a2 = Announcement::from_bytes(&bytes).unwrap(); - assert_eq!(a1, a2); - } - - #[proptest] - fn response_randomness_to_bytes_from_bytes_test(r1: ResponseRandomness) { - let bytes = r1.to_bytes(); - let r2 = ResponseRandomness::from_bytes(&bytes).unwrap(); - assert_eq!(r1, r2); - } } diff --git a/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs b/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs new file mode 100644 index 00000000000..8d8519dea9f --- /dev/null +++ b/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs @@ -0,0 +1,88 @@ +//! Voter objects decoding implementation. + +use std::io::Read; + +use anyhow::anyhow; + +use super::{proof::VoterProof, EncryptedVote}; +use crate::crypto::{elgamal::Ciphertext, zk_unit_vector::UnitVectorProof}; + +impl EncryptedVote { + /// Decode `EncryptedVote` from bytes. + /// + /// # Errors + /// - Cannot decode ciphertext. + pub fn from_bytes(mut bytes: &[u8], size: usize) -> anyhow::Result { + let mut ciph_buf = [0u8; Ciphertext::BYTES_SIZE]; + + let ciphertexts = (0..size) + .map(|i| { + bytes.read_exact(&mut ciph_buf)?; + Ciphertext::from_bytes(&ciph_buf) + .map_err(|e| anyhow!("Cannot decode ciphertext at {i}, error: {e}")) + }) + .collect::>()?; + + Ok(Self(ciphertexts)) + } + + /// Get a deserialized bytes size + #[must_use] + pub fn bytes_size(&self) -> usize { + self.0.len() * Ciphertext::BYTES_SIZE + } + + /// Encode `EncryptedVote` tos bytes. + #[must_use] + pub fn to_bytes(&self) -> Vec { + let mut res = Vec::with_capacity(self.bytes_size()); + self.0 + .iter() + .for_each(|c| res.extend_from_slice(&c.to_bytes())); + res + } +} + +impl VoterProof { + /// Decode `VoterProof` from bytes. + /// + /// # Errors + /// - Cannot decode announcement value. + /// - Cannot decode ciphertext value. + /// - Cannot decode response randomness value. + /// - Cannot decode scalar value. + pub fn from_bytes(bytes: &[u8], size: usize) -> anyhow::Result { + UnitVectorProof::from_bytes(bytes, size).map(Self) + } + + /// Get a deserialized bytes size + #[must_use] + pub fn bytes_size(&self) -> usize { + self.0.bytes_size() + } + + /// Encode `EncryptedVote` tos bytes. + #[must_use] + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } +} + +#[cfg(test)] +mod tests { + use proptest::sample::size_range; + use test_strategy::proptest; + + use super::*; + + #[proptest] + fn encrypted_vote_to_bytes_from_bytes_test( + #[any(size_range(0..u8::MAX as usize).lift())] ciphers: Vec, + ) { + let vote1 = EncryptedVote(ciphers); + let bytes = vote1.to_bytes(); + assert_eq!(bytes.len(), vote1.bytes_size()); + let vote2 = EncryptedVote::from_bytes(&bytes, vote1.0.len()).unwrap(); + assert_eq!(vote1, vote2); + } +} diff --git a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index 0f9a1973287..1537dbdcb2c 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -1,10 +1,9 @@ //! Module containing all primitives related to the voter. +mod decoding; pub mod proof; -use std::io::Read; - -use anyhow::{anyhow, ensure}; +use anyhow::ensure; use rand_core::CryptoRngCore; use crate::crypto::{ @@ -40,40 +39,6 @@ impl EncryptionRandomness { } impl EncryptedVote { - /// Decode `EncryptedVote` from bytes. - /// - /// # Errors - /// - Cannot decode ciphertext. - pub fn from_bytes(mut bytes: &[u8], size: usize) -> anyhow::Result { - let mut ciph_buf = [0u8; Ciphertext::BYTES_SIZE]; - - let ciphertexts = (0..size) - .map(|i| { - bytes.read_exact(&mut ciph_buf)?; - Ciphertext::from_bytes(&ciph_buf) - .map_err(|e| anyhow!("Cannot decode ciphertext at {i}, error: {e}")) - }) - .collect::>()?; - - Ok(Self(ciphertexts)) - } - - /// Get a deserialized bytes size - #[must_use] - pub fn bytes_size(&self) -> usize { - self.0.len() * Ciphertext::BYTES_SIZE - } - - /// Encode `EncryptedVote` tos bytes. - #[must_use] - pub fn to_bytes(&self) -> Vec { - let mut res = Vec::with_capacity(self.bytes_size()); - self.0 - .iter() - .for_each(|c| res.extend_from_slice(&c.to_bytes())); - res - } - /// Get the ciphertext to the corresponding `voting_option`. pub(crate) fn get_ciphertext_for_choice(&self, voting_option: usize) -> Option<&Ciphertext> { self.0.get(voting_option) @@ -132,22 +97,8 @@ pub fn encrypt_vote( #[cfg(test)] mod tests { - use proptest::sample::size_range; - use test_strategy::proptest; - use super::*; - #[proptest] - fn encrypted_vote_to_bytes_from_bytes_test( - #[any(size_range(0..u8::MAX as usize).lift())] ciphers: Vec, - ) { - let vote1 = EncryptedVote(ciphers); - let bytes = vote1.to_bytes(); - assert_eq!(bytes.len(), vote1.bytes_size()); - let vote2 = EncryptedVote::from_bytes(&bytes, vote1.0.len()).unwrap(); - assert_eq!(vote1, vote2); - } - #[test] fn vote_test() { let voting_options = 3; diff --git a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs index 6855420ffae..f9a70b50e80 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs @@ -18,32 +18,7 @@ use crate::{ /// Tally proof struct. #[allow(clippy::module_name_repetitions)] #[derive(Debug, Clone, PartialEq, Eq)] -pub struct VoterProof(UnitVectorProof); - -impl VoterProof { - /// Decode `VoterProof` from bytes. - /// - /// # Errors - /// - Cannot decode announcement value. - /// - Cannot decode ciphertext value. - /// - Cannot decode response randomness value. - /// - Cannot decode scalar value. - pub fn from_bytes(bytes: &[u8], size: usize) -> anyhow::Result { - UnitVectorProof::from_bytes(bytes, size).map(Self) - } - - /// Get a deserialized bytes size - #[must_use] - pub fn bytes_size(&self) -> usize { - self.0.bytes_size() - } - - /// Encode `EncryptedVote` tos bytes. - #[must_use] - pub fn to_bytes(&self) -> Vec { - self.0.to_bytes() - } -} +pub struct VoterProof(pub(super) UnitVectorProof); /// Voter proof commitment struct. pub struct VoterProofCommitment(GroupElement); From 43b506ecd3531adc5dc6126e2c564b4ec27744bd Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 10 Oct 2024 13:37:48 +0300 Subject: [PATCH 11/44] wip --- .../src/crypto/elgamal/decoding.rs | 54 +++++- .../src/crypto/zk_unit_vector/decoding.rs | 21 ++- rust/catalyst-voting/src/txs/mod.rs | 3 +- rust/catalyst-voting/src/txs/utils.rs | 3 - rust/catalyst-voting/src/txs/v1.rs | 139 ---------------- rust/catalyst-voting/src/txs/v1/decoding.rs | 154 ++++++++++++++++++ rust/catalyst-voting/src/txs/v1/mod.rs | 46 ++++++ .../src/vote_protocol/voter/decoding.rs | 24 ++- .../src/vote_protocol/voter/mod.rs | 16 ++ 9 files changed, 302 insertions(+), 158 deletions(-) delete mode 100644 rust/catalyst-voting/src/txs/utils.rs delete mode 100644 rust/catalyst-voting/src/txs/v1.rs create mode 100644 rust/catalyst-voting/src/txs/v1/decoding.rs create mode 100644 rust/catalyst-voting/src/txs/v1/mod.rs diff --git a/rust/catalyst-voting/src/crypto/elgamal/decoding.rs b/rust/catalyst-voting/src/crypto/elgamal/decoding.rs index b9f094d85b9..e2d82309cef 100644 --- a/rust/catalyst-voting/src/crypto/elgamal/decoding.rs +++ b/rust/catalyst-voting/src/crypto/elgamal/decoding.rs @@ -2,7 +2,45 @@ use anyhow::anyhow; -use super::{Ciphertext, GroupElement}; +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) + } +} impl Ciphertext { /// `Ciphertext` bytes size @@ -16,7 +54,7 @@ impl Ciphertext { res } - /// Attempt to construct a `Scalar` from a compressed value byte representation. + /// Attempt to construct a `Ciphertext` from a byte representation. /// /// # Errors /// - Cannot decode group element field. @@ -37,6 +75,18 @@ 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/zk_unit_vector/decoding.rs b/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs index 3554e03e3aa..916484a0c6d 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs @@ -7,6 +7,13 @@ use anyhow::anyhow; use super::{Announcement, Ciphertext, GroupElement, ResponseRandomness, Scalar, UnitVectorProof}; impl UnitVectorProof { + /// Get an underlying vector length. + /// + /// **Note** each vector field has the same length. + pub fn size(&self) -> usize { + self.0.len() + } + /// Decode `UnitVectorProof` from bytes. /// /// # Errors @@ -14,26 +21,26 @@ impl UnitVectorProof { /// - Cannot decode ciphertext value. /// - Cannot decode response randomness value. /// - Cannot decode scalar value. - pub fn from_bytes(mut bytes: &[u8], size: usize) -> anyhow::Result { + pub fn from_bytes(mut bytes: &[u8], len: usize) -> anyhow::Result { let mut ann_buf = [0u8; Announcement::BYTES_SIZE]; let mut dl_buf = [0u8; Ciphertext::BYTES_SIZE]; let mut rr_buf = [0u8; ResponseRandomness::BYTES_SIZE]; - let ann = (0..size) + let ann = (0..len) .map(|i| { bytes.read_exact(&mut ann_buf)?; Announcement::from_bytes(&ann_buf) .map_err(|e| anyhow!("Cannot decode announcement at {i}, error: {e}.")) }) .collect::>()?; - let dl = (0..size) + let dl = (0..len) .map(|i| { bytes.read_exact(&mut dl_buf)?; Ciphertext::from_bytes(&dl_buf) .map_err(|e| anyhow!("Cannot decode ciphertext at {i}, error: {e}.")) }) .collect::>()?; - let rr = (0..size) + let rr = (0..len) .map(|i| { bytes.read_exact(&mut rr_buf)?; ResponseRandomness::from_bytes(&rr_buf) @@ -141,9 +148,11 @@ mod tests { use super::*; #[proptest] - fn proof_to_bytes_from_bytes_test(p1: UnitVectorProof) { + fn proof_to_bytes_from_bytes_test( + #[strategy(0..20usize)] _size: usize, #[any(#_size)] p1: UnitVectorProof, + ) { let bytes = p1.to_bytes(); - let p2 = UnitVectorProof::from_bytes(&bytes, p1.0.len()).unwrap(); + let p2 = UnitVectorProof::from_bytes(&bytes, p1.size()).unwrap(); assert_eq!(p1, p2); } diff --git a/rust/catalyst-voting/src/txs/mod.rs b/rust/catalyst-voting/src/txs/mod.rs index c430e0d2941..71e3abe6036 100644 --- a/rust/catalyst-voting/src/txs/mod.rs +++ b/rust/catalyst-voting/src/txs/mod.rs @@ -1,4 +1,3 @@ //! A catalyst transaction objects implementation -mod utils; -// pub mod v1; +pub mod v1; diff --git a/rust/catalyst-voting/src/txs/utils.rs b/rust/catalyst-voting/src/txs/utils.rs deleted file mode 100644 index fec47ef7566..00000000000 --- a/rust/catalyst-voting/src/txs/utils.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Utility functions for catalyst transactions module - -#![allow(missing_docs, clippy::missing_docs_in_private_items)] diff --git a/rust/catalyst-voting/src/txs/v1.rs b/rust/catalyst-voting/src/txs/v1.rs deleted file mode 100644 index a22bd00db38..00000000000 --- a/rust/catalyst-voting/src/txs/v1.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! 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)] - -use std::io::Read; - -use crate::vote_protocol::voter::{proof::VoterProof, EncryptedVote}; - -/// A v1 (Jörmungandr) transaction struct -pub struct Tx { - /// Vote plan id - vote_plan_id: [u8; 32], - /// Proposal index - proposal_index: u8, - /// Vote - vote: Vote, -} - -/// Vote struct -pub enum Vote { - /// Public voting choice - Public(u8), - /// Private (encrypted) voting choice - Private(EncryptedVote, VoterProof), -} - -/// `Tx` decoding error -#[derive(thiserror::Error, Debug)] -pub enum DecodingError { - /// `std::io::Error` - #[error(transparent)] - IoRead(#[from] std::io::Error), - /// Invalid padding tag - #[error("Invalid padding tag field value, must be equals to `0`, provided: {0}.")] - InvalidPaddingTag(u8), - /// Invalid fragment tag - #[error("Invalid fragment tag field value, must be equals to `11`, provided: {0}.")] - InvalidFragmentTag(u8), - /// Cannot decode vote tag - #[error("Invalid vote tag value, must be equals to `0` or `1`, provided: {0}")] - InvalidVoteTag(u8), - /// Cannot decode encrypted vote - #[error("Cannot decode ecnrypted vote field.")] - CannotDecodeEncryptedVote, - /// Cannot decode voter proof field - #[error("Cannot decode voter proof field.")] - CannotDecodeVoterProof, - /// Invalid number of inputs - #[error("Invalid number of inputs, expected: `1`, provided: {0}")] - InvalidNumberOfInputs(u8), - /// Invalid number of outputs - #[error("Invalid number of outputs, expected: `0`, provided: {0}")] - InvalidNumberOfOutputs(u8), - /// Invalid input tag - #[error("Invalid input tag, expected: `255`, provided: {0}")] - InvalidInputTag(u8), -} - -impl Tx { - /// Decode `Tx` from bytes. - /// - /// # Errors - /// - `DecodingError` - #[allow(clippy::indexing_slicing)] - pub fn from_bytes(mut bytes: &[u8]) -> Result { - let mut u8_buf = [0u8; 1]; - let mut u32_buf = [0u8; 4]; - let mut u64_buf = [0u8; 8]; - let mut u256_buf = [0u8; 32]; - // let mut u512_buf = [0u8; 64]; - - bytes.read_exact(&mut u32_buf)?; - let tx_size = u32::from_be_bytes(u32_buf); - - bytes.read_exact(&mut u8_buf)?; - if u8_buf[0] != 0 { - return Err(DecodingError::InvalidPaddingTag(u8_buf[0])); - } - - bytes.read_exact(&mut u8_buf)?; - if u8_buf[0] != 11 { - return Err(DecodingError::InvalidFragmentTag(u8_buf[0])); - } - - bytes.read_exact(&mut u256_buf)?; - let vote_plan_id = u256_buf; - - bytes.read_exact(&mut u8_buf)?; - let proposal_index = u8_buf[0]; - - bytes.read_exact(&mut u8_buf)?; - let vote = match u8_buf[0] { - 1 => { - bytes.read_exact(&mut u8_buf)?; - Vote::Public(u8_buf[0]) - }, - 2 => { - bytes.read_exact(&mut u8_buf)?; - let vote = EncryptedVote::from_bytes(bytes, u8_buf[0].into()) - .ok_or(DecodingError::CannotDecodeEncryptedVote)?; - bytes = &bytes[vote.bytes_size()..]; - - bytes.read_exact(&mut u8_buf)?; - let proof = VoterProof::from_bytes(bytes, u8_buf[0].into()) - .ok_or(DecodingError::CannotDecodeVoterProof)?; - bytes = &bytes[vote.bytes_size()..]; - - Vote::Private(vote, proof) - }, - tag => return Err(DecodingError::InvalidVoteTag(tag)), - }; - - // skip block date (epoch and slot) - bytes.read_exact(&mut u64_buf)?; - - bytes.read_exact(&mut u8_buf)?; - if u8_buf[0] != 1 { - return Err(DecodingError::InvalidNumberOfInputs(u8_buf[0])); - } - bytes.read_exact(&mut u8_buf)?; - if u8_buf[0] != 0 { - return Err(DecodingError::InvalidNumberOfOutputs(u8_buf[0])); - } - - bytes.read_exact(&mut u8_buf)?; - if u8_buf[0] != 0xFF { - return Err(DecodingError::InvalidInputTag(u8_buf[0])); - } - - // skip value - bytes.read_exact(&mut u64_buf)?; - - Ok(Self { - vote_plan_id, - proposal_index, - vote, - }) - } -} diff --git a/rust/catalyst-voting/src/txs/v1/decoding.rs b/rust/catalyst-voting/src/txs/v1/decoding.rs new file mode 100644 index 00000000000..2006a9b73c7 --- /dev/null +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -0,0 +1,154 @@ +//! V1 transaction objects decoding implementation. + +use std::io::Read; + +use anyhow::{anyhow, bail, ensure}; + +use super::{EncryptedVote, PublicKey, Tx, Vote, VoterProof}; + +impl Tx { + /// 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 tx_body = vec![0, 11]; + + tx_body.extend_from_slice(&self.vote_plan_id); + tx_body.push(self.proposal_index); + + match &self.vote { + Vote::Public(vote) => { + // Public vote tag + tx_body.push(1); + tx_body.push(*vote); + }, + Vote::Private(vote, proof) => { + // Private vote tag + tx_body.push(2); + tx_body.push(vote.size() as u8); + tx_body.extend_from_slice(&vote.to_bytes()); + + tx_body.push(proof.size() as u8); + tx_body.extend_from_slice(&proof.to_bytes()); + }, + } + + // Zeros block date + tx_body.extend_from_slice(&[0u8; 8]); + // Number of inputs + tx_body.push(1); + // Number of outputs + tx_body.push(0); + // Input tag + tx_body.push(0xFF); + // Zero value + tx_body.extend_from_slice(&[0u8; 8]); + tx_body.extend_from_slice(&self.public_key.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); + res + } + + /// Attempt to construct a `Tx` from a byte representation. + /// + /// # Errors + /// - Invalid padding tag field value. + /// - Invalid fragment tag field value. + /// - Invalid encrypted vote. + /// - Invalid voter proof. + /// - Invalid vote tag value. + /// - Invalid public key. + #[allow(clippy::indexing_slicing)] + pub fn from_bytes(mut bytes: &[u8]) -> anyhow::Result { + let mut u8_buf = [0u8; 1]; + let mut u32_buf = [0u8; 4]; + let mut u64_buf = [0u8; 8]; + let mut u256_buf = [0u8; 32]; + + // Read tx size + bytes.read_exact(&mut u32_buf)?; + + bytes.read_exact(&mut u8_buf)?; + ensure!( + u8_buf[0] == 0, + "Invalid padding tag field value, must be equals to `0`, provided: {0}.", + u8_buf[0] + ); + + bytes.read_exact(&mut u8_buf)?; + ensure!( + u8_buf[0] == 11, + "Invalid fragment tag field value, must be equals to `11`, provided: {0}.", + u8_buf[0] + ); + + bytes.read_exact(&mut u256_buf)?; + let vote_plan_id = u256_buf; + + bytes.read_exact(&mut u8_buf)?; + let proposal_index = u8_buf[0]; + + bytes.read_exact(&mut u8_buf)?; + let vote = match u8_buf[0] { + 1 => { + bytes.read_exact(&mut u8_buf)?; + Vote::Public(u8_buf[0]) + }, + 2 => { + bytes.read_exact(&mut u8_buf)?; + let vote = EncryptedVote::from_bytes(bytes, u8_buf[0].into()) + .map_err(|e| anyhow!("Invalid encrypted vote, error: {e}."))?; + bytes = &bytes[vote.bytes_size()..]; + + bytes.read_exact(&mut u8_buf)?; + let proof = VoterProof::from_bytes(bytes, u8_buf[0].into()) + .map_err(|e| anyhow!("Invalid voter proof, error: {e}."))?; + bytes = &bytes[vote.bytes_size()..]; + + Vote::Private(vote, proof) + }, + tag => bail!("Invalid vote tag value, must be equals to `0` or `1`, provided: {tag}"), + }; + + // skip block date (epoch and slot) + bytes.read_exact(&mut u64_buf)?; + + bytes.read_exact(&mut u8_buf)?; + ensure!( + u8_buf[0] == 1, + "Invalid number of inputs, expected: `1`, provided: {0}", + u8_buf[0] + ); + + bytes.read_exact(&mut u8_buf)?; + ensure!( + u8_buf[0] == 0, + "Invalid number of outputs, expected: `0`, provided: {0}", + u8_buf[0] + ); + + bytes.read_exact(&mut u8_buf)?; + ensure!( + u8_buf[0] == 0xFF, + "Invalid input tag, expected: `255`, provided: {0}", + u8_buf[0] + ); + + // skip value + bytes.read_exact(&mut u64_buf)?; + + bytes.read_exact(&mut u256_buf)?; + let public_key = PublicKey::from_bytes(&u256_buf) + .map_err(|e| anyhow!("Invalid public key, error: {e}."))?; + + Ok(Self { + vote_plan_id, + proposal_index, + vote, + public_key, + }) + } +} diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs new file mode 100644 index 00000000000..eadffe80e54 --- /dev/null +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -0,0 +1,46 @@ +//! 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)] + +mod decoding; + +use crate::{ + vote_protocol::voter::{proof::VoterProof, EncryptedVote}, + PublicKey, +}; + +/// A v1 (Jörmungandr) transaction struct +pub struct Tx { + /// Vote plan id + vote_plan_id: [u8; 32], + /// Proposal index + proposal_index: u8, + /// Vote + vote: Vote, + /// Public key + public_key: PublicKey, +} + +/// Vote struct +pub enum Vote { + /// Public voting choice + Public(u8), + /// Private (encrypted) voting choice + Private(EncryptedVote, VoterProof), +} + +// #[cfg(test)] +// mod tests { +// use proptest::prelude::{Arbitrary, BoxedStrategy}; + +// use super::*; + +// impl Arbitrary for Tx { +// type Parameters = (); +// type Strategy = BoxedStrategy; + +// fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + +// } +// } +// } diff --git a/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs b/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs index 8d8519dea9f..b67e99dfdff 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs @@ -8,6 +8,11 @@ use super::{proof::VoterProof, EncryptedVote}; use crate::crypto::{elgamal::Ciphertext, zk_unit_vector::UnitVectorProof}; impl EncryptedVote { + /// Get an underlying vector length. + pub(crate) fn size(&self) -> usize { + self.0.len() + } + /// Decode `EncryptedVote` from bytes. /// /// # Errors @@ -44,6 +49,14 @@ impl EncryptedVote { } impl VoterProof { + /// Get an underlying vector length. + /// + /// **Note** each vector field has the same length. + #[must_use] + pub fn size(&self) -> usize { + self.0.size() + } + /// Decode `VoterProof` from bytes. /// /// # Errors @@ -51,8 +64,8 @@ impl VoterProof { /// - Cannot decode ciphertext value. /// - Cannot decode response randomness value. /// - Cannot decode scalar value. - pub fn from_bytes(bytes: &[u8], size: usize) -> anyhow::Result { - UnitVectorProof::from_bytes(bytes, size).map(Self) + pub fn from_bytes(bytes: &[u8], len: usize) -> anyhow::Result { + UnitVectorProof::from_bytes(bytes, len).map(Self) } /// Get a deserialized bytes size @@ -70,19 +83,18 @@ impl VoterProof { #[cfg(test)] mod tests { - use proptest::sample::size_range; use test_strategy::proptest; use super::*; #[proptest] fn encrypted_vote_to_bytes_from_bytes_test( - #[any(size_range(0..u8::MAX as usize).lift())] ciphers: Vec, + #[strategy(0..20usize)] _size: usize, #[any(#_size)] vote1: EncryptedVote, ) { - let vote1 = EncryptedVote(ciphers); + println!("{}", vote1.size()); let bytes = vote1.to_bytes(); assert_eq!(bytes.len(), vote1.bytes_size()); - let vote2 = EncryptedVote::from_bytes(&bytes, vote1.0.len()).unwrap(); + let vote2 = EncryptedVote::from_bytes(&bytes, vote1.size()).unwrap(); assert_eq!(vote1, vote2); } } diff --git a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index 1537dbdcb2c..788bcca2940 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -97,8 +97,24 @@ pub fn encrypt_vote( #[cfg(test)] mod tests { + use proptest::{ + prelude::{any_with, Arbitrary, BoxedStrategy, Strategy}, + sample::size_range, + }; + use super::*; + impl Arbitrary for EncryptedVote { + type Parameters = usize; + type Strategy = BoxedStrategy; + + fn arbitrary_with(size: Self::Parameters) -> Self::Strategy { + any_with::>((size_range(size), ())) + .prop_map(Self) + .boxed() + } + } + #[test] fn vote_test() { let voting_options = 3; From f40f1a896d4c9e228cdac8f1da2eba89ed61c261 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 10 Oct 2024 14:47:18 +0300 Subject: [PATCH 12/44] add test --- .../src/crypto/zk_unit_vector/decoding.rs | 2 +- rust/catalyst-voting/src/txs/v1/decoding.rs | 60 +++++++++++++++++++ rust/catalyst-voting/src/txs/v1/mod.rs | 18 +----- .../src/vote_protocol/voter/decoding.rs | 3 +- 4 files changed, 64 insertions(+), 19 deletions(-) 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 916484a0c6d..2e80507b8fc 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs @@ -149,7 +149,7 @@ mod tests { #[proptest] fn proof_to_bytes_from_bytes_test( - #[strategy(0..20usize)] _size: usize, #[any(#_size)] p1: UnitVectorProof, + #[strategy(0..5usize)] _size: usize, #[any(#_size)] p1: UnitVectorProof, ) { let bytes = p1.to_bytes(); let p2 = UnitVectorProof::from_bytes(&bytes, p1.size()).unwrap(); diff --git a/rust/catalyst-voting/src/txs/v1/decoding.rs b/rust/catalyst-voting/src/txs/v1/decoding.rs index 2006a9b73c7..e2755ecae40 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -152,3 +152,63 @@ impl Tx { }) } } + +#[cfg(test)] +mod tests { + use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, ProptestConfig, Strategy}; + use test_strategy::proptest; + + 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() + } + } + + 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() + } + } + + #[proptest(ProptestConfig::with_cases(1))] + #[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()); + assert_eq!(size as usize, bytes.len() - 4); + let t2 = Tx::from_bytes(&bytes).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 eadffe80e54..a1c00d38549 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -10,6 +10,7 @@ use crate::{ }; /// A v1 (Jörmungandr) transaction struct +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Tx { /// Vote plan id vote_plan_id: [u8; 32], @@ -22,25 +23,10 @@ pub struct Tx { } /// Vote struct +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Vote { /// Public voting choice Public(u8), /// Private (encrypted) voting choice Private(EncryptedVote, VoterProof), } - -// #[cfg(test)] -// mod tests { -// use proptest::prelude::{Arbitrary, BoxedStrategy}; - -// use super::*; - -// impl Arbitrary for Tx { -// type Parameters = (); -// type Strategy = BoxedStrategy; - -// fn arbitrary_with((): Self::Parameters) -> Self::Strategy { - -// } -// } -// } diff --git a/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs b/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs index b67e99dfdff..72e9d8179c3 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs @@ -89,9 +89,8 @@ mod tests { #[proptest] fn encrypted_vote_to_bytes_from_bytes_test( - #[strategy(0..20usize)] _size: usize, #[any(#_size)] vote1: EncryptedVote, + #[strategy(0..5usize)] _size: usize, #[any(#_size)] vote1: EncryptedVote, ) { - println!("{}", vote1.size()); let bytes = vote1.to_bytes(); assert_eq!(bytes.len(), vote1.bytes_size()); let vote2 = EncryptedVote::from_bytes(&bytes, vote1.size()).unwrap(); From adfccba1ae0084f35c80eeca1b43cb68c8cbe2fd Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 10 Oct 2024 16:07:32 +0300 Subject: [PATCH 13/44] fix --- .../src/crypto/zk_unit_vector/decoding.rs | 4 +++- rust/catalyst-voting/src/txs/v1/decoding.rs | 15 +++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) 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 2e80507b8fc..e693f18eb7b 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs @@ -58,7 +58,8 @@ impl UnitVectorProof { /// Get a deserialized bytes size #[must_use] pub fn bytes_size(&self) -> usize { - self.0.len() * Announcement::BYTES_SIZE + Scalar::BYTES_SIZE + + self.0.len() * Announcement::BYTES_SIZE + self.0.len() * Ciphertext::BYTES_SIZE + self.0.len() * ResponseRandomness::BYTES_SIZE } @@ -152,6 +153,7 @@ mod tests { #[strategy(0..5usize)] _size: usize, #[any(#_size)] p1: UnitVectorProof, ) { let bytes = p1.to_bytes(); + assert_eq!(bytes.len(), p1.bytes_size()); let p2 = UnitVectorProof::from_bytes(&bytes, p1.size()).unwrap(); assert_eq!(p1, p2); } diff --git a/rust/catalyst-voting/src/txs/v1/decoding.rs b/rust/catalyst-voting/src/txs/v1/decoding.rs index e2755ecae40..732df80c1e9 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -70,6 +70,12 @@ impl Tx { // Read tx size bytes.read_exact(&mut u32_buf)?; + let tx_size = u32::from_be_bytes(u32_buf); + ensure!( + tx_size as usize == bytes.len(), + "Invalid tx size, expected: {tx_size}, provided: {0}.", + bytes.len() + ); bytes.read_exact(&mut u8_buf)?; ensure!( @@ -106,7 +112,7 @@ impl Tx { bytes.read_exact(&mut u8_buf)?; let proof = VoterProof::from_bytes(bytes, u8_buf[0].into()) .map_err(|e| anyhow!("Invalid voter proof, error: {e}."))?; - bytes = &bytes[vote.bytes_size()..]; + bytes = &bytes[proof.bytes_size()..]; Vote::Private(vote, proof) }, @@ -155,7 +161,7 @@ impl Tx { #[cfg(test)] mod tests { - use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, ProptestConfig, Strategy}; + use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, Strategy}; use test_strategy::proptest; use super::*; @@ -201,13 +207,10 @@ mod tests { } } - #[proptest(ProptestConfig::with_cases(1))] + #[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()); - assert_eq!(size as usize, bytes.len() - 4); let t2 = Tx::from_bytes(&bytes).unwrap(); assert_eq!(t1, t2); } From 1d7afdfb345c9db2a625434b33337828ef1a4883 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 10 Oct 2024 16:48:42 +0300 Subject: [PATCH 14/44] refactor --- .../src/crypto/zk_unit_vector/decoding.rs | 16 +++--- rust/catalyst-voting/src/txs/v1/decoding.rs | 56 +++++++++---------- .../src/vote_protocol/voter/decoding.rs | 18 +++--- 3 files changed, 44 insertions(+), 46 deletions(-) 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 e693f18eb7b..0bcb222ffde 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs @@ -21,35 +21,35 @@ impl UnitVectorProof { /// - Cannot decode ciphertext value. /// - Cannot decode response randomness value. /// - Cannot decode scalar value. - pub fn from_bytes(mut bytes: &[u8], len: usize) -> anyhow::Result { + pub fn from_bytes(reader: &mut R, len: usize) -> anyhow::Result { let mut ann_buf = [0u8; Announcement::BYTES_SIZE]; let mut dl_buf = [0u8; Ciphertext::BYTES_SIZE]; let mut rr_buf = [0u8; ResponseRandomness::BYTES_SIZE]; let ann = (0..len) .map(|i| { - bytes.read_exact(&mut ann_buf)?; + reader.read_exact(&mut ann_buf)?; Announcement::from_bytes(&ann_buf) .map_err(|e| anyhow!("Cannot decode announcement at {i}, error: {e}.")) }) .collect::>()?; let dl = (0..len) .map(|i| { - bytes.read_exact(&mut dl_buf)?; + reader.read_exact(&mut dl_buf)?; Ciphertext::from_bytes(&dl_buf) .map_err(|e| anyhow!("Cannot decode ciphertext at {i}, error: {e}.")) }) .collect::>()?; let rr = (0..len) .map(|i| { - bytes.read_exact(&mut rr_buf)?; + reader.read_exact(&mut rr_buf)?; ResponseRandomness::from_bytes(&rr_buf) .map_err(|e| anyhow!("Cannot decode response randomness at {i}, error: {e}.")) }) .collect::>()?; let mut scalar_buf = [0u8; Scalar::BYTES_SIZE]; - bytes.read_exact(&mut scalar_buf)?; + reader.read_exact(&mut scalar_buf)?; let scalar = Scalar::from_bytes(scalar_buf).map_err(|_| anyhow!("Cannot decode scalar field."))?; Ok(Self(ann, dl, rr, scalar)) @@ -57,7 +57,7 @@ impl UnitVectorProof { /// Get a deserialized bytes size #[must_use] - pub fn bytes_size(&self) -> usize { + fn bytes_size(&self) -> usize { Scalar::BYTES_SIZE + self.0.len() * Announcement::BYTES_SIZE + self.0.len() * Ciphertext::BYTES_SIZE @@ -144,6 +144,8 @@ impl ResponseRandomness { #[cfg(test)] mod tests { + use std::io::Cursor; + use test_strategy::proptest; use super::*; @@ -154,7 +156,7 @@ mod tests { ) { let bytes = p1.to_bytes(); assert_eq!(bytes.len(), p1.bytes_size()); - let p2 = UnitVectorProof::from_bytes(&bytes, p1.size()).unwrap(); + let p2 = UnitVectorProof::from_bytes(&mut Cursor::new(bytes), p1.size()).unwrap(); assert_eq!(p1, p2); } diff --git a/rust/catalyst-voting/src/txs/v1/decoding.rs b/rust/catalyst-voting/src/txs/v1/decoding.rs index 732df80c1e9..d6d66260b7e 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -44,6 +44,7 @@ impl Tx { tx_body.push(0xFF); // Zero value tx_body.extend_from_slice(&[0u8; 8]); + tx_body.extend_from_slice(&self.public_key.to_bytes()); // Add the size of decoded bytes to the beginning. @@ -62,57 +63,49 @@ impl Tx { /// - Invalid vote tag value. /// - Invalid public key. #[allow(clippy::indexing_slicing)] - pub fn from_bytes(mut bytes: &[u8]) -> anyhow::Result { + pub fn from_bytes(reader: &mut R) -> anyhow::Result { let mut u8_buf = [0u8; 1]; let mut u32_buf = [0u8; 4]; let mut u64_buf = [0u8; 8]; let mut u256_buf = [0u8; 32]; - // Read tx size - bytes.read_exact(&mut u32_buf)?; - let tx_size = u32::from_be_bytes(u32_buf); - ensure!( - tx_size as usize == bytes.len(), - "Invalid tx size, expected: {tx_size}, provided: {0}.", - bytes.len() - ); + // Skip tx size field + reader.read_exact(&mut u32_buf)?; - bytes.read_exact(&mut u8_buf)?; + reader.read_exact(&mut u8_buf)?; ensure!( u8_buf[0] == 0, "Invalid padding tag field value, must be equals to `0`, provided: {0}.", u8_buf[0] ); - bytes.read_exact(&mut u8_buf)?; + reader.read_exact(&mut u8_buf)?; ensure!( u8_buf[0] == 11, "Invalid fragment tag field value, must be equals to `11`, provided: {0}.", u8_buf[0] ); - bytes.read_exact(&mut u256_buf)?; + reader.read_exact(&mut u256_buf)?; let vote_plan_id = u256_buf; - bytes.read_exact(&mut u8_buf)?; + reader.read_exact(&mut u8_buf)?; let proposal_index = u8_buf[0]; - bytes.read_exact(&mut u8_buf)?; + reader.read_exact(&mut u8_buf)?; let vote = match u8_buf[0] { 1 => { - bytes.read_exact(&mut u8_buf)?; + reader.read_exact(&mut u8_buf)?; Vote::Public(u8_buf[0]) }, 2 => { - bytes.read_exact(&mut u8_buf)?; - let vote = EncryptedVote::from_bytes(bytes, u8_buf[0].into()) + reader.read_exact(&mut u8_buf)?; + let vote = EncryptedVote::from_bytes(reader, u8_buf[0].into()) .map_err(|e| anyhow!("Invalid encrypted vote, error: {e}."))?; - bytes = &bytes[vote.bytes_size()..]; - bytes.read_exact(&mut u8_buf)?; - let proof = VoterProof::from_bytes(bytes, u8_buf[0].into()) + reader.read_exact(&mut u8_buf)?; + let proof = VoterProof::from_bytes(reader, u8_buf[0].into()) .map_err(|e| anyhow!("Invalid voter proof, error: {e}."))?; - bytes = &bytes[proof.bytes_size()..]; Vote::Private(vote, proof) }, @@ -120,23 +113,23 @@ impl Tx { }; // skip block date (epoch and slot) - bytes.read_exact(&mut u64_buf)?; + reader.read_exact(&mut u64_buf)?; - bytes.read_exact(&mut u8_buf)?; + reader.read_exact(&mut u8_buf)?; ensure!( u8_buf[0] == 1, "Invalid number of inputs, expected: `1`, provided: {0}", u8_buf[0] ); - bytes.read_exact(&mut u8_buf)?; + reader.read_exact(&mut u8_buf)?; ensure!( u8_buf[0] == 0, "Invalid number of outputs, expected: `0`, provided: {0}", u8_buf[0] ); - bytes.read_exact(&mut u8_buf)?; + reader.read_exact(&mut u8_buf)?; ensure!( u8_buf[0] == 0xFF, "Invalid input tag, expected: `255`, provided: {0}", @@ -144,9 +137,9 @@ impl Tx { ); // skip value - bytes.read_exact(&mut u64_buf)?; + reader.read_exact(&mut u64_buf)?; - bytes.read_exact(&mut u256_buf)?; + reader.read_exact(&mut u256_buf)?; let public_key = PublicKey::from_bytes(&u256_buf) .map_err(|e| anyhow!("Invalid public key, error: {e}."))?; @@ -161,6 +154,8 @@ impl Tx { #[cfg(test)] mod tests { + use std::io::Cursor; + use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, Strategy}; use test_strategy::proptest; @@ -211,7 +206,12 @@ mod tests { #[allow(clippy::indexing_slicing)] fn tx_to_bytes_from_bytes_test(t1: Tx) { let bytes = t1.to_bytes(); - let t2 = Tx::from_bytes(&bytes).unwrap(); + + // verify correctness serializing tx size field + let size = u32::from_be_bytes(bytes[0..4].try_into().unwrap()); + assert_eq!(size as usize, bytes.len() - 4); + + let t2 = Tx::from_bytes(&mut Cursor::new(bytes)).unwrap(); assert_eq!(t1, t2); } } diff --git a/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs b/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs index 72e9d8179c3..37bbf36fa56 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs @@ -17,12 +17,12 @@ impl EncryptedVote { /// /// # Errors /// - Cannot decode ciphertext. - pub fn from_bytes(mut bytes: &[u8], size: usize) -> anyhow::Result { + pub fn from_bytes(reader: &mut R, size: usize) -> anyhow::Result { let mut ciph_buf = [0u8; Ciphertext::BYTES_SIZE]; let ciphertexts = (0..size) .map(|i| { - bytes.read_exact(&mut ciph_buf)?; + reader.read_exact(&mut ciph_buf)?; Ciphertext::from_bytes(&ciph_buf) .map_err(|e| anyhow!("Cannot decode ciphertext at {i}, error: {e}")) }) @@ -64,14 +64,8 @@ impl VoterProof { /// - Cannot decode ciphertext value. /// - Cannot decode response randomness value. /// - Cannot decode scalar value. - pub fn from_bytes(bytes: &[u8], len: usize) -> anyhow::Result { - UnitVectorProof::from_bytes(bytes, len).map(Self) - } - - /// Get a deserialized bytes size - #[must_use] - pub fn bytes_size(&self) -> usize { - self.0.bytes_size() + pub fn from_bytes(reader: &mut R, len: usize) -> anyhow::Result { + UnitVectorProof::from_bytes(reader, len).map(Self) } /// Encode `EncryptedVote` tos bytes. @@ -83,6 +77,8 @@ impl VoterProof { #[cfg(test)] mod tests { + use std::io::Cursor; + use test_strategy::proptest; use super::*; @@ -93,7 +89,7 @@ mod tests { ) { let bytes = vote1.to_bytes(); assert_eq!(bytes.len(), vote1.bytes_size()); - let vote2 = EncryptedVote::from_bytes(&bytes, vote1.size()).unwrap(); + let vote2 = EncryptedVote::from_bytes(&mut Cursor::new(bytes), vote1.size()).unwrap(); assert_eq!(vote1, vote2); } } From 7c670a0ae6288b8043ed4bb75d406592ce7fe944 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 10 Oct 2024 16:59:09 +0300 Subject: [PATCH 15/44] fix tests --- rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs | 4 +--- rust/catalyst-voting/src/txs/v1/decoding.rs | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) 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 0bcb222ffde..a050fb2a41b 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs @@ -144,8 +144,6 @@ impl ResponseRandomness { #[cfg(test)] mod tests { - use std::io::Cursor; - use test_strategy::proptest; use super::*; @@ -156,7 +154,7 @@ mod tests { ) { let bytes = p1.to_bytes(); assert_eq!(bytes.len(), p1.bytes_size()); - let p2 = UnitVectorProof::from_bytes(&mut Cursor::new(bytes), p1.size()).unwrap(); + let p2 = UnitVectorProof::from_bytes(&mut bytes.as_slice(), p1.size()).unwrap(); assert_eq!(p1, p2); } diff --git a/rust/catalyst-voting/src/txs/v1/decoding.rs b/rust/catalyst-voting/src/txs/v1/decoding.rs index d6d66260b7e..ac6d6ec3bdb 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -154,8 +154,6 @@ impl Tx { #[cfg(test)] mod tests { - use std::io::Cursor; - use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, Strategy}; use test_strategy::proptest; @@ -211,7 +209,7 @@ mod tests { let size = u32::from_be_bytes(bytes[0..4].try_into().unwrap()); assert_eq!(size as usize, bytes.len() - 4); - let t2 = Tx::from_bytes(&mut Cursor::new(bytes)).unwrap(); + let t2 = Tx::from_bytes(&mut bytes.as_slice()).unwrap(); assert_eq!(t1, t2); } } From 7ab36ba8bb303171f3b8c3584bbb31b9817ec3fe Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 10 Oct 2024 17:23:09 +0300 Subject: [PATCH 16/44] wip --- .../src/crypto/zk_unit_vector/decoding.rs | 22 +++--- rust/catalyst-voting/src/lib.rs | 1 + rust/catalyst-voting/src/txs/v1/decoding.rs | 73 ++++++++----------- rust/catalyst-voting/src/utils.rs | 35 +++++++++ .../src/vote_protocol/voter/decoding.rs | 11 +-- 5 files changed, 82 insertions(+), 60 deletions(-) create mode 100644 rust/catalyst-voting/src/utils.rs 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 a050fb2a41b..82b1229fb3c 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs @@ -5,6 +5,7 @@ use std::io::Read; use anyhow::anyhow; use super::{Announcement, Ciphertext, GroupElement, ResponseRandomness, Scalar, UnitVectorProof}; +use crate::utils::read_array; impl UnitVectorProof { /// Get an underlying vector length. @@ -22,36 +23,31 @@ impl UnitVectorProof { /// - Cannot decode response randomness value. /// - Cannot decode scalar value. pub fn from_bytes(reader: &mut R, len: usize) -> anyhow::Result { - let mut ann_buf = [0u8; Announcement::BYTES_SIZE]; - let mut dl_buf = [0u8; Ciphertext::BYTES_SIZE]; - let mut rr_buf = [0u8; ResponseRandomness::BYTES_SIZE]; - let ann = (0..len) .map(|i| { - reader.read_exact(&mut ann_buf)?; - Announcement::from_bytes(&ann_buf) + let bytes = read_array(reader)?; + Announcement::from_bytes(&bytes) .map_err(|e| anyhow!("Cannot decode announcement at {i}, error: {e}.")) }) .collect::>()?; let dl = (0..len) .map(|i| { - reader.read_exact(&mut dl_buf)?; - Ciphertext::from_bytes(&dl_buf) + let bytes = read_array(reader)?; + Ciphertext::from_bytes(&bytes) .map_err(|e| anyhow!("Cannot decode ciphertext at {i}, error: {e}.")) }) .collect::>()?; let rr = (0..len) .map(|i| { - reader.read_exact(&mut rr_buf)?; - ResponseRandomness::from_bytes(&rr_buf) + let bytes = read_array(reader)?; + ResponseRandomness::from_bytes(&bytes) .map_err(|e| anyhow!("Cannot decode response randomness at {i}, error: {e}.")) }) .collect::>()?; - let mut scalar_buf = [0u8; Scalar::BYTES_SIZE]; - reader.read_exact(&mut scalar_buf)?; + let bytes = read_array(reader)?; let scalar = - Scalar::from_bytes(scalar_buf).map_err(|_| anyhow!("Cannot decode scalar field."))?; + Scalar::from_bytes(bytes).map_err(|_| anyhow!("Cannot decode scalar field."))?; Ok(Self(ann, dl, rr, scalar)) } diff --git a/rust/catalyst-voting/src/lib.rs b/rust/catalyst-voting/src/lib.rs index ac06b9e8dd8..b494392750a 100644 --- a/rust/catalyst-voting/src/lib.rs +++ b/rust/catalyst-voting/src/lib.rs @@ -2,6 +2,7 @@ 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 ac6d6ec3bdb..60527d47e9d 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -5,6 +5,7 @@ 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}; impl Tx { /// Convert this `Tx` to its underlying sequence of bytes. @@ -64,47 +65,38 @@ impl Tx { /// - Invalid public key. #[allow(clippy::indexing_slicing)] pub fn from_bytes(reader: &mut R) -> anyhow::Result { - let mut u8_buf = [0u8; 1]; - let mut u32_buf = [0u8; 4]; - let mut u64_buf = [0u8; 8]; - let mut u256_buf = [0u8; 32]; - // Skip tx size field - reader.read_exact(&mut u32_buf)?; + read_be_u32(reader)?; - reader.read_exact(&mut u8_buf)?; + let padding_tag = read_be_u8(reader)?; ensure!( - u8_buf[0] == 0, - "Invalid padding tag field value, must be equals to `0`, provided: {0}.", - u8_buf[0] + padding_tag == 0, + "Invalid padding tag field value, must be equals to `0`, provided: {padding_tag}.", ); - reader.read_exact(&mut u8_buf)?; + let fragment_tag = read_be_u8(reader)?; ensure!( - u8_buf[0] == 11, - "Invalid fragment tag field value, must be equals to `11`, provided: {0}.", - u8_buf[0] + fragment_tag == 11, + "Invalid fragment tag field value, must be equals to `11`, provided: {fragment_tag}.", ); - reader.read_exact(&mut u256_buf)?; - let vote_plan_id = u256_buf; + let vote_plan_id = read_array(reader)?; - reader.read_exact(&mut u8_buf)?; - let proposal_index = u8_buf[0]; + let proposal_index = read_be_u8(reader)?; - reader.read_exact(&mut u8_buf)?; - let vote = match u8_buf[0] { + let vote_tag = read_be_u8(reader)?; + let vote = match vote_tag { 1 => { - reader.read_exact(&mut u8_buf)?; - Vote::Public(u8_buf[0]) + let vote = read_be_u8(reader)?; + Vote::Public(vote) }, 2 => { - reader.read_exact(&mut u8_buf)?; - let vote = EncryptedVote::from_bytes(reader, u8_buf[0].into()) + let size = read_be_u8(reader)?; + let vote = EncryptedVote::from_bytes(reader, size.into()) .map_err(|e| anyhow!("Invalid encrypted vote, error: {e}."))?; - reader.read_exact(&mut u8_buf)?; - let proof = VoterProof::from_bytes(reader, u8_buf[0].into()) + let size = read_be_u8(reader)?; + let proof = VoterProof::from_bytes(reader, size.into()) .map_err(|e| anyhow!("Invalid voter proof, error: {e}."))?; Vote::Private(vote, proof) @@ -113,34 +105,31 @@ impl Tx { }; // skip block date (epoch and slot) - reader.read_exact(&mut u64_buf)?; + read_be_u64(reader)?; - reader.read_exact(&mut u8_buf)?; + let inputs_amount = read_be_u8(reader)?; ensure!( - u8_buf[0] == 1, - "Invalid number of inputs, expected: `1`, provided: {0}", - u8_buf[0] + inputs_amount == 1, + "Invalid number of inputs, expected: `1`, provided: {inputs_amount}", ); - reader.read_exact(&mut u8_buf)?; + let outputs_amount = read_be_u8(reader)?; ensure!( - u8_buf[0] == 0, - "Invalid number of outputs, expected: `0`, provided: {0}", - u8_buf[0] + outputs_amount == 0, + "Invalid number of outputs, expected: `0`, provided: {outputs_amount}", ); - reader.read_exact(&mut u8_buf)?; + let input_tag = read_be_u8(reader)?; ensure!( - u8_buf[0] == 0xFF, - "Invalid input tag, expected: `255`, provided: {0}", - u8_buf[0] + input_tag == 0xFF, + "Invalid input tag, expected: `255`, provided: {input_tag}", ); // skip value - reader.read_exact(&mut u64_buf)?; + read_be_u64(reader)?; - reader.read_exact(&mut u256_buf)?; - let public_key = PublicKey::from_bytes(&u256_buf) + let public_key_bytes = read_array(reader)?; + let public_key = PublicKey::from_bytes(&public_key_bytes) .map_err(|e| anyhow!("Invalid public key, error: {e}."))?; Ok(Self { diff --git a/rust/catalyst-voting/src/utils.rs b/rust/catalyst-voting/src/utils.rs new file mode 100644 index 00000000000..393c00fb71f --- /dev/null +++ b/rust/catalyst-voting/src/utils.rs @@ -0,0 +1,35 @@ +//! Utility functions. + +use std::io::Read; + +/// Read a single byte from the reader. +#[inline] +pub(crate) fn read_be_u8(reader: &mut R) -> anyhow::Result { + let mut buf = [0u8; 1]; + reader.read_exact(&mut buf)?; + Ok(u8::from_be_bytes(buf)) +} + +/// Read a big-endian u32 from the reader. +#[inline] +pub(crate) fn read_be_u32(reader: &mut R) -> anyhow::Result { + let mut buf = [0u8; 4]; + reader.read_exact(&mut buf)?; + Ok(u32::from_be_bytes(buf)) +} + +/// Read a big-endian u64 from the reader. +#[inline] +pub(crate) fn read_be_u64(reader: &mut R) -> anyhow::Result { + let mut buf = [0u8; 8]; + reader.read_exact(&mut buf)?; + Ok(u64::from_be_bytes(buf)) +} + +/// Read a N-byte array from the reader. +#[inline] +pub(crate) fn read_array(reader: &mut R) -> anyhow::Result<[u8; N]> { + let mut buf = [0u8; N]; + reader.read_exact(&mut buf)?; + Ok(buf) +} diff --git a/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs b/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs index 37bbf36fa56..f4f08cc866d 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs @@ -5,7 +5,10 @@ use std::io::Read; use anyhow::anyhow; use super::{proof::VoterProof, EncryptedVote}; -use crate::crypto::{elgamal::Ciphertext, zk_unit_vector::UnitVectorProof}; +use crate::{ + crypto::{elgamal::Ciphertext, zk_unit_vector::UnitVectorProof}, + utils::read_array, +}; impl EncryptedVote { /// Get an underlying vector length. @@ -18,12 +21,10 @@ impl EncryptedVote { /// # Errors /// - Cannot decode ciphertext. pub fn from_bytes(reader: &mut R, size: usize) -> anyhow::Result { - let mut ciph_buf = [0u8; Ciphertext::BYTES_SIZE]; - let ciphertexts = (0..size) .map(|i| { - reader.read_exact(&mut ciph_buf)?; - Ciphertext::from_bytes(&ciph_buf) + let bytes = read_array(reader)?; + Ciphertext::from_bytes(&bytes) .map_err(|e| anyhow!("Cannot decode ciphertext at {i}, error: {e}")) }) .collect::>()?; From bc6d4a9ec9e8c5cd3c3c8e20bff36b7f0f74b18c Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Fri, 11 Oct 2024 09:57:42 +0300 Subject: [PATCH 17/44] wip --- rust/catalyst-voting/src/txs/v1/decoding.rs | 42 ------------------ rust/catalyst-voting/src/txs/v1/mod.rs | 48 +++++++++++++++++++++ 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/rust/catalyst-voting/src/txs/v1/decoding.rs b/rust/catalyst-voting/src/txs/v1/decoding.rs index 60527d47e9d..467927632e8 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -143,51 +143,9 @@ impl Tx { #[cfg(test)] mod tests { - use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, Strategy}; use test_strategy::proptest; 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() - } - } - - 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() - } - } #[proptest] #[allow(clippy::indexing_slicing)] diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index a1c00d38549..a78a1db10c0 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -30,3 +30,51 @@ pub enum Vote { /// Private (encrypted) voting choice Private(EncryptedVote, VoterProof), } + +#[cfg(test)] +mod tests { + use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, Strategy}; + + 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() + } + } + + 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() + } + } +} From ca5ca2a3aec1531587b34c7b56cfd22f9a55f7eb Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Fri, 11 Oct 2024 10:04:13 +0300 Subject: [PATCH 18/44] fix spelling --- rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 968988ef0c4..1f2c21bb42d 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs @@ -254,8 +254,8 @@ mod tests { )>(((size_range(size), (((), ()), ())), ())) .prop_map(|(val, scalar)| { let (vec, rr): (Vec<_>, Vec<_>) = val.into_iter().unzip(); - let (an, ciph) = vec.into_iter().unzip(); - Self(an, ciph, rr, scalar) + let (an, cipher) = vec.into_iter().unzip(); + Self(an, cipher, rr, scalar) }) .boxed() } From ceb11d81a48ad51f4513ad126d73ef4e65d64539 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Fri, 11 Oct 2024 12:41:09 +0300 Subject: [PATCH 19/44] add v1::Tx generation functions --- rust/catalyst-voting/Cargo.toml | 1 + .../src/crypto/group/ristretto255/mod.rs | 6 ++ rust/catalyst-voting/src/txs/v1/decoding.rs | 10 +-- rust/catalyst-voting/src/txs/v1/mod.rs | 76 ++++++++++++++++--- .../src/vote_protocol/voter/proof.rs | 7 ++ 5 files changed, 85 insertions(+), 15 deletions(-) diff --git a/rust/catalyst-voting/Cargo.toml b/rust/catalyst-voting/Cargo.toml index df6e2aa051c..7d250e5f949 100644 --- a/rust/catalyst-voting/Cargo.toml +++ b/rust/catalyst-voting/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] anyhow = "1.0.89" rand_core = "0.6.4" +rand_chacha = "0.3.1" curve25519-dalek = { version = "4.1.3", features = ["digest"] } blake2b_simd = "1.0.2" diff --git a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs index b636f8a8648..abda6851f3f 100644 --- a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs +++ b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs @@ -86,6 +86,12 @@ impl GroupElement { pub fn zero() -> Self { GroupElement(Point::identity()) } + + /// Generate a `GroupElement` from a hash digest. + pub fn from_hash(hash: D) -> GroupElement + where D: Digest + Default { + GroupElement(Point::from_hash(hash)) + } } // `std::ops` traits implementations diff --git a/rust/catalyst-voting/src/txs/v1/decoding.rs b/rust/catalyst-voting/src/txs/v1/decoding.rs index 467927632e8..17ddc90a561 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -4,7 +4,7 @@ use std::io::Read; use anyhow::{anyhow, bail, ensure}; -use super::{EncryptedVote, PublicKey, Tx, Vote, VoterProof}; +use super::{EncryptedVote, PublicKey, Tx, VotePayload, VoterProof}; use crate::utils::{read_array, read_be_u32, read_be_u64, read_be_u8}; impl Tx { @@ -19,12 +19,12 @@ impl Tx { tx_body.push(self.proposal_index); match &self.vote { - Vote::Public(vote) => { + VotePayload::Public(vote) => { // Public vote tag tx_body.push(1); tx_body.push(*vote); }, - Vote::Private(vote, proof) => { + VotePayload::Private(vote, proof) => { // Private vote tag tx_body.push(2); tx_body.push(vote.size() as u8); @@ -88,7 +88,7 @@ impl Tx { let vote = match vote_tag { 1 => { let vote = read_be_u8(reader)?; - Vote::Public(vote) + VotePayload::Public(vote) }, 2 => { let size = read_be_u8(reader)?; @@ -99,7 +99,7 @@ impl Tx { 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!("Invalid vote tag value, must be equals to `0` or `1`, provided: {tag}"), }; diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index a78a1db10c0..55bd79d7338 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -1,11 +1,18 @@ //! 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)] - mod decoding; +use curve25519_dalek::digest::Update; +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; + use crate::{ - vote_protocol::voter::{proof::VoterProof, EncryptedVote}, + crypto::hash::Blake2b512Hasher, + vote_protocol::voter::{ + encrypt_vote, + proof::{generate_voter_proof, VoterProof, VoterProofCommitment}, + EncryptedVote, Vote, + }, PublicKey, }; @@ -17,20 +24,69 @@ pub struct Tx { /// Proposal index proposal_index: u8, /// Vote - vote: Vote, + vote: VotePayload, /// Public key public_key: PublicKey, } -/// Vote struct +/// Vote payload struct. +/// Contains all necesarry 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), } +impl Tx { + /// Generate a new `Tx` with public vote + #[must_use] + pub fn new_public( + vote_plan_id: [u8; 32], proposal_index: u8, choice: u8, users_public_key: PublicKey, + ) -> Self { + Self { + vote_plan_id, + proposal_index, + vote: VotePayload::Public(choice), + public_key: users_public_key, + } + } + + /// Generate a new `Tx` with public vote + /// + /// # Errors + /// - Invalid voting choice + pub fn new_private( + vote_plan_id: [u8; 32], proposal_index: u8, proposal_voting_options: u8, choice: u8, + users_public_key: PublicKey, election_public_key: &PublicKey, + ) -> anyhow::Result { + let vote = Vote::new(choice.into(), proposal_voting_options.into())?; + + let mut rng = ChaCha20Rng::from_entropy(); + let (encrypted_vote, randomness) = encrypt_vote(&vote, election_public_key, &mut rng); + + let vote_plan_id_hash = Blake2b512Hasher::new().chain(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, + &mut rng, + )?; + + Ok(Self { + vote_plan_id, + proposal_index, + vote: VotePayload::Private(encrypted_vote, voter_proof), + public_key: users_public_key, + }) + } +} + #[cfg(test)] mod tests { use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, Strategy}; @@ -43,7 +99,7 @@ mod tests { type Strategy = BoxedStrategy; fn arbitrary_with((): Self::Parameters) -> Self::Strategy { - any::<([u8; 32], u8, Vote, SecretKey)>() + any::<([u8; 32], u8, VotePayload, SecretKey)>() .prop_map(|(vote_plan_id, proposal_index, vote, s)| { Tx { vote_plan_id, @@ -56,7 +112,7 @@ mod tests { } } - impl Arbitrary for Vote { + impl Arbitrary for VotePayload { type Parameters = (); type Strategy = BoxedStrategy; @@ -64,12 +120,12 @@ mod tests { any::() .prop_flat_map(|b| { if b { - any::().prop_map(Vote::Public).boxed() + 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)| Vote::Private(v, p)) + .prop_map(|(v, p)| VotePayload::Private(v, p)) }) .boxed() } diff --git a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs index f9a70b50e80..e6d3d230e73 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs @@ -4,6 +4,7 @@ use std::ops::Mul; use anyhow::ensure; +use curve25519_dalek::digest::{consts::U64, Digest}; use rand_core::CryptoRngCore; use super::{EncryptedVote, EncryptionRandomness, Vote}; @@ -28,6 +29,12 @@ impl VoterProofCommitment { pub fn random(rng: &mut R) -> Self { Self(GroupElement::GENERATOR.mul(&Scalar::random(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. From e5190fd53baff1f2e1775253d8b2f9bcfb979216 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Fri, 11 Oct 2024 15:03:31 +0300 Subject: [PATCH 20/44] add test --- rust/catalyst-voting/src/txs/v1/mod.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index 55bd79d7338..845513b6938 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -90,6 +90,7 @@ impl Tx { #[cfg(test)] mod tests { use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, Strategy}; + use test_strategy::proptest; use super::*; use crate::SecretKey; @@ -133,4 +134,24 @@ mod tests { .boxed() } } + + #[proptest] + fn tx_private_test( + vote_plan_id: [u8; 32], proposal_index: u8, #[strategy(1u8..)] proposal_voting_options: u8, + #[strategy(0..#proposal_voting_options)] choice: u8, users_secret_key: SecretKey, + election_secret_key: SecretKey, + ) { + let users_public_key = users_secret_key.public_key(); + let election_public_key = election_secret_key.public_key(); + + Tx::new_private( + vote_plan_id, + proposal_index, + proposal_voting_options, + choice, + users_public_key, + &election_public_key, + ) + .unwrap(); + } } From 38f20f3c0478ab88978410eaba71b88463896c8d Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Oct 2024 12:27:12 +0300 Subject: [PATCH 21/44] refactor, add ElectionPublicKey, ElectionSecretKey --- .../src/crypto/elgamal/decoding.rs | 52 +------------- .../catalyst-voting/src/crypto/elgamal/mod.rs | 69 ++++--------------- .../src/crypto/zk_unit_vector/challenges.rs | 11 ++- .../src/crypto/zk_unit_vector/mod.rs | 22 +++--- rust/catalyst-voting/src/lib.rs | 2 - rust/catalyst-voting/src/txs/v1/decoding.rs | 11 ++- rust/catalyst-voting/src/txs/v1/mod.rs | 37 +++++----- .../src/vote_protocol/committee.rs | 54 +++++++++++++++ rust/catalyst-voting/src/vote_protocol/mod.rs | 1 + .../src/vote_protocol/tally/mod.rs | 7 +- .../src/vote_protocol/tally/proof.rs | 15 ++-- .../src/vote_protocol/voter/mod.rs | 7 +- .../src/vote_protocol/voter/proof.rs | 12 ++-- rust/catalyst-voting/tests/voting_test.rs | 26 ++++--- 14 files changed, 138 insertions(+), 188 deletions(-) create mode 100644 rust/catalyst-voting/src/vote_protocol/committee.rs diff --git a/rust/catalyst-voting/src/crypto/elgamal/decoding.rs b/rust/catalyst-voting/src/crypto/elgamal/decoding.rs index e2d82309cef..786ab0bae42 100644 --- a/rust/catalyst-voting/src/crypto/elgamal/decoding.rs +++ b/rust/catalyst-voting/src/crypto/elgamal/decoding.rs @@ -2,45 +2,7 @@ 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 @@ -75,18 +37,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..4d82bd04739 100644 --- a/rust/catalyst-voting/src/crypto/elgamal/mod.rs +++ b/rust/catalyst-voting/src/crypto/elgamal/mod.rs @@ -3,53 +3,14 @@ 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. #[derive(Debug, Clone, PartialEq, Eq)] 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 +29,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. /// 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 /// `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 +75,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 +109,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/zk_unit_vector/challenges.rs b/rust/catalyst-voting/src/crypto/zk_unit_vector/challenges.rs index 5033e8b2e43..3237d41ff82 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/challenges.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/challenges.rs @@ -2,17 +2,14 @@ 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::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/mod.rs b/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs index 1f2c21bb42d..ef2d099edf4 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs @@ -21,7 +21,7 @@ use randomness_announcements::{Announcement, BlindingRandomness, ResponseRandomn use utils::get_bit; use crate::crypto::{ - elgamal::{encrypt, Ciphertext, PublicKey}, + elgamal::{encrypt, Ciphertext}, group::{GroupElement, Scalar}, }; @@ -45,8 +45,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 +104,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(); @@ -158,7 +158,7 @@ fn generate_response( /// Verify a unit vector proof. 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 +197,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 +241,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 +269,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 +319,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 +330,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..febae3db85a 100644 --- a/rust/catalyst-voting/src/lib.rs +++ b/rust/catalyst-voting/src/lib.rs @@ -4,5 +4,3 @@ 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 17ddc90a561..127a4ef0bc2 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -4,7 +4,7 @@ use std::io::Read; use anyhow::{anyhow, bail, ensure}; -use super::{EncryptedVote, PublicKey, Tx, VotePayload, VoterProof}; +use super::{EncryptedVote, Tx, VotePayload, VoterProof}; use crate::utils::{read_array, read_be_u32, read_be_u64, read_be_u8}; impl Tx { @@ -46,7 +46,7 @@ impl Tx { // Zero value tx_body.extend_from_slice(&[0u8; 8]); - tx_body.extend_from_slice(&self.public_key.to_bytes()); + // tx_body.extend_from_slice(&self.public_key.to_bytes()); // Add the size of decoded bytes to the beginning. let mut res = (tx_body.len() as u32).to_be_bytes().to_vec(); @@ -128,15 +128,14 @@ impl Tx { // skip value read_be_u64(reader)?; - let public_key_bytes = read_array(reader)?; - let public_key = PublicKey::from_bytes(&public_key_bytes) - .map_err(|e| anyhow!("Invalid public key, error: {e}."))?; + // let public_key_bytes = read_array(reader)?; + // let public_key = PublicKey::from_bytes(&public_key_bytes) + // .map_err(|e| anyhow!("Invalid public key, error: {e}."))?; Ok(Self { vote_plan_id, proposal_index, vote, - public_key, }) } } diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index 845513b6938..52b68e47e50 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -8,12 +8,14 @@ use rand_core::SeedableRng; use crate::{ crypto::hash::Blake2b512Hasher, - vote_protocol::voter::{ - encrypt_vote, - proof::{generate_voter_proof, VoterProof, VoterProofCommitment}, - EncryptedVote, Vote, + vote_protocol::{ + committee::ElectionPublicKey, + voter::{ + encrypt_vote, + proof::{generate_voter_proof, VoterProof, VoterProofCommitment}, + EncryptedVote, Vote, + }, }, - PublicKey, }; /// A v1 (Jörmungandr) transaction struct @@ -25,8 +27,8 @@ pub struct Tx { proposal_index: u8, /// Vote vote: VotePayload, - /// Public key - public_key: PublicKey, + // /// Public key + // public_key: PublicKey, } /// Vote payload struct. @@ -42,14 +44,11 @@ pub enum VotePayload { impl Tx { /// Generate a new `Tx` with public vote #[must_use] - pub fn new_public( - vote_plan_id: [u8; 32], proposal_index: u8, choice: u8, users_public_key: PublicKey, - ) -> Self { + pub fn new_public(vote_plan_id: [u8; 32], proposal_index: u8, choice: u8) -> Self { Self { vote_plan_id, proposal_index, vote: VotePayload::Public(choice), - public_key: users_public_key, } } @@ -59,7 +58,7 @@ impl Tx { /// - Invalid voting choice pub fn new_private( vote_plan_id: [u8; 32], proposal_index: u8, proposal_voting_options: u8, choice: u8, - users_public_key: PublicKey, election_public_key: &PublicKey, + election_public_key: &ElectionPublicKey, ) -> anyhow::Result { let vote = Vote::new(choice.into(), proposal_voting_options.into())?; @@ -82,7 +81,6 @@ impl Tx { vote_plan_id, proposal_index, vote: VotePayload::Private(encrypted_vote, voter_proof), - public_key: users_public_key, }) } } @@ -93,20 +91,19 @@ mod tests { use test_strategy::proptest; use super::*; - use crate::SecretKey; + use crate::vote_protocol::committee::ElectionSecretKey; impl Arbitrary for Tx { type Parameters = (); type Strategy = BoxedStrategy; fn arbitrary_with((): Self::Parameters) -> Self::Strategy { - any::<([u8; 32], u8, VotePayload, SecretKey)>() - .prop_map(|(vote_plan_id, proposal_index, vote, s)| { + any::<([u8; 32], u8, VotePayload)>() + .prop_map(|(vote_plan_id, proposal_index, vote)| { Tx { vote_plan_id, proposal_index, vote, - public_key: s.public_key(), } }) .boxed() @@ -138,10 +135,9 @@ mod tests { #[proptest] fn tx_private_test( vote_plan_id: [u8; 32], proposal_index: u8, #[strategy(1u8..)] proposal_voting_options: u8, - #[strategy(0..#proposal_voting_options)] choice: u8, users_secret_key: SecretKey, - election_secret_key: SecretKey, + #[strategy(0..#proposal_voting_options)] choice: u8, + election_secret_key: ElectionSecretKey, ) { - let users_public_key = users_secret_key.public_key(); let election_public_key = election_secret_key.public_key(); Tx::new_private( @@ -149,7 +145,6 @@ mod tests { proposal_index, proposal_voting_options, choice, - users_public_key, &election_public_key, ) .unwrap(); 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..8fd0e0d0b2d --- /dev/null +++ b/rust/catalyst-voting/src/vote_protocol/committee.rs @@ -0,0 +1,54 @@ +//! Module containing all primitives related to the committee. + +use std::ops::Mul; + +use rand_core::CryptoRngCore; + +use crate::crypto::{ + 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`. + pub fn random(rng: &mut R) -> Self { + Self(Scalar::random(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); + +impl ElectionPublicKey { + /// Randomly generate the `ElectionPublicKey`. + pub fn random(rng: &mut R) -> Self { + Self(GroupElement::GENERATOR.mul(&Scalar::random(rng))) + } +} + +#[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..5bba1a46697 100644 --- a/rust/catalyst-voting/src/vote_protocol/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/mod.rs @@ -89,5 +89,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..e035fc1f92d 100644 --- a/rust/catalyst-voting/src/vote_protocol/tally/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/tally/proof.rs @@ -11,7 +11,7 @@ use crate::{ group::{GroupElement, Scalar}, zk_dl_equality::{generate_dleq_proof, verify_dleq_proof, DleqProof}, }, - PublicKey, SecretKey, + vote_protocol::committee::{ElectionPublicKey, ElectionSecretKey}, }; /// Tally proof struct. @@ -22,18 +22,18 @@ pub struct TallyProof(DleqProof); /// 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, ); @@ -45,12 +45,13 @@ pub fn generate_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..a8d141a09f7 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -6,8 +6,9 @@ pub mod proof; use anyhow::ensure; use rand_core::CryptoRngCore; +use super::committee::ElectionPublicKey; use crate::crypto::{ - elgamal::{encrypt, Ciphertext, PublicKey}, + elgamal::{encrypt, Ciphertext}, group::Scalar, }; @@ -81,7 +82,7 @@ impl Vote { /// # Errors /// - `EncryptedVoteError` 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,7 +90,7 @@ 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) diff --git a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs index e6d3d230e73..69d61550207 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs @@ -13,7 +13,7 @@ use crate::{ group::{GroupElement, Scalar}, zk_unit_vector::{generate_unit_vector_proof, verify_unit_vector_proof, UnitVectorProof}, }, - PublicKey, + vote_protocol::committee::ElectionPublicKey, }; /// Tally proof struct. @@ -46,7 +46,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(), @@ -61,7 +61,7 @@ pub fn generate_voter_proof( &vote.to_unit_vector(), encrypted_vote.0, randomness.0, - public_key, + &public_key.0, &commitment.0, rng, ); @@ -73,10 +73,10 @@ pub fn generate_voter_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..5b5e2efda1c 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, verify_tally_proof}, + tally, DecryptionTallySetup, + }, + voter::{ + encrypt_vote, + proof::{generate_voter_proof, verify_voter_proof, VoterProofCommitment}, + Vote, }, - SecretKey, }; use proptest::prelude::ProptestConfig; use test_strategy::{proptest, Arbitrary}; @@ -32,7 +30,7 @@ struct Voter { 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(&mut rng); let election_public_key = election_secret_key.public_key(); let voter_proof_commitment = VoterProofCommitment::random(&mut rng); From 040730d8ae7c9a1d983cd1a4445b6a8a6bec7ea5 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Oct 2024 12:32:43 +0300 Subject: [PATCH 22/44] fix --- rust/catalyst-voting/src/lib.rs | 2 +- rust/catalyst-voting/src/vote_protocol/mod.rs | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/rust/catalyst-voting/src/lib.rs b/rust/catalyst-voting/src/lib.rs index febae3db85a..e0d911e7180 100644 --- a/rust/catalyst-voting/src/lib.rs +++ b/rust/catalyst-voting/src/lib.rs @@ -1,6 +1,6 @@ //! Voting primitives which are used among Catalyst ecosystem. -mod crypto; +pub mod crypto; pub mod txs; mod utils; pub mod vote_protocol; diff --git a/rust/catalyst-voting/src/vote_protocol/mod.rs b/rust/catalyst-voting/src/vote_protocol/mod.rs index 5bba1a46697..3f26bbd07da 100644 --- a/rust/catalyst-voting/src/vote_protocol/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/mod.rs @@ -1,16 +1,14 @@ //! 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, -//! }, -//! voter::{encrypt_vote, Vote}, +//! use catalyst_voting::vote_protocol::{ +//! committee::ElectionSecretKey, +//! tally::{ +//! decrypt_tally, +//! proof::{generate_tally_proof, verify_tally_proof}, +//! tally, DecryptionTallySetup, //! }, -//! SecretKey, +//! voter::{encrypt_vote, Vote}, //! }; //! //! struct Voter { @@ -20,7 +18,7 @@ //! //! let mut rng = rand_core::OsRng; //! let voting_options = 3; -//! let election_secret_key = SecretKey::random(&mut rng); +//! let election_secret_key = ElectionSecretKey::random(&mut rng); //! let election_public_key = election_secret_key.public_key(); //! //! let voter_1 = Voter { From f096bada3f868cb672ceef85d2853899ec68c21f Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Oct 2024 12:54:35 +0300 Subject: [PATCH 23/44] fix with must_use --- rust/catalyst-voting/src/crypto/elgamal/decoding.rs | 3 +++ rust/catalyst-voting/src/crypto/elgamal/mod.rs | 1 + rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs | 2 ++ rust/catalyst-voting/src/crypto/hash.rs | 1 + rust/catalyst-voting/src/crypto/zk_dl_equality.rs | 2 ++ rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs | 1 + rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs | 2 ++ rust/catalyst-voting/src/txs/v1/mod.rs | 4 ++-- rust/catalyst-voting/src/vote_protocol/tally/proof.rs | 1 + rust/catalyst-voting/src/vote_protocol/voter/proof.rs | 2 ++ 10 files changed, 17 insertions(+), 2 deletions(-) diff --git a/rust/catalyst-voting/src/crypto/elgamal/decoding.rs b/rust/catalyst-voting/src/crypto/elgamal/decoding.rs index 786ab0bae42..52e207f84c3 100644 --- a/rust/catalyst-voting/src/crypto/elgamal/decoding.rs +++ b/rust/catalyst-voting/src/crypto/elgamal/decoding.rs @@ -9,6 +9,7 @@ impl Ciphertext { 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()); @@ -20,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( diff --git a/rust/catalyst-voting/src/crypto/elgamal/mod.rs b/rust/catalyst-voting/src/crypto/elgamal/mod.rs index 4d82bd04739..01aa76529d4 100644 --- a/rust/catalyst-voting/src/crypto/elgamal/mod.rs +++ b/rust/catalyst-voting/src/crypto/elgamal/mod.rs @@ -9,6 +9,7 @@ use crate::crypto::group::{GroupElement, Scalar}; /// ``ElGamal`` ciphertext, encrypted message with the public key. #[derive(Debug, Clone, PartialEq, Eq)] +#[must_use] pub struct Ciphertext(GroupElement, GroupElement); impl Ciphertext { diff --git a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs index abda6851f3f..103f9a4fd0f 100644 --- a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs +++ b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs @@ -20,10 +20,12 @@ use rand_core::CryptoRngCore; /// Ristretto group scalar. #[derive(Debug, Clone, PartialEq, Eq)] +#[must_use] pub struct Scalar(IScalar); /// Ristretto group element. #[derive(Debug, Clone, PartialEq, Eq)] +#[must_use] pub struct GroupElement(Point); impl From for Scalar { diff --git a/rust/catalyst-voting/src/crypto/hash.rs b/rust/catalyst-voting/src/crypto/hash.rs index 93a6aff3215..b8d5b64be34 100644 --- a/rust/catalyst-voting/src/crypto/hash.rs +++ b/rust/catalyst-voting/src/crypto/hash.rs @@ -6,6 +6,7 @@ use curve25519_dalek::digest::{ /// Blake2b-512 hasher instance. #[derive(Clone, Debug)] +#[must_use] pub struct Blake2b512Hasher(blake2b_simd::State); impl Blake2b512Hasher { diff --git a/rust/catalyst-voting/src/crypto/zk_dl_equality.rs b/rust/catalyst-voting/src/crypto/zk_dl_equality.rs index 8b17aa55851..9e1d4d75277 100644 --- a/rust/catalyst-voting/src/crypto/zk_dl_equality.rs +++ b/rust/catalyst-voting/src/crypto/zk_dl_equality.rs @@ -20,6 +20,7 @@ use crate::crypto::{ }; /// DLEQ proof struct +#[must_use] pub struct DleqProof(Scalar, Scalar); /// Generates a DLEQ proof. @@ -37,6 +38,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/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 ef2d099edf4..1e85ad2dd79 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs @@ -27,6 +27,7 @@ use crate::crypto::{ /// Unit vector proof struct #[derive(Debug, Clone, PartialEq, Eq)] +#[must_use] pub struct UnitVectorProof( Vec, Vec, @@ -157,6 +158,7 @@ fn generate_response( } /// Verify a unit vector proof. +#[must_use] pub fn verify_unit_vector_proof( proof: &UnitVectorProof, mut ciphertexts: Vec, public_key: &GroupElement, commitment_key: &GroupElement, diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index 52b68e47e50..6079103c42a 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -20,6 +20,7 @@ use crate::{ /// A v1 (Jörmungandr) transaction struct #[derive(Debug, Clone, PartialEq, Eq)] +#[must_use] pub struct Tx { /// Vote plan id vote_plan_id: [u8; 32], @@ -43,7 +44,6 @@ pub enum VotePayload { impl Tx { /// Generate a new `Tx` with public vote - #[must_use] pub fn new_public(vote_plan_id: [u8; 32], proposal_index: u8, choice: u8) -> Self { Self { vote_plan_id, @@ -140,7 +140,7 @@ mod tests { ) { let election_public_key = election_secret_key.public_key(); - Tx::new_private( + let _tx = Tx::new_private( vote_plan_id, proposal_index, proposal_voting_options, diff --git a/rust/catalyst-voting/src/vote_protocol/tally/proof.rs b/rust/catalyst-voting/src/vote_protocol/tally/proof.rs index e035fc1f92d..3f69866b3b4 100644 --- a/rust/catalyst-voting/src/vote_protocol/tally/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/tally/proof.rs @@ -16,6 +16,7 @@ use crate::{ /// Tally proof struct. #[allow(clippy::module_name_repetitions)] +#[must_use] pub struct TallyProof(DleqProof); /// Generates a tally proof. diff --git a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs index 69d61550207..f72f28fbee8 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs @@ -19,9 +19,11 @@ use crate::{ /// 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 { From d063c41cbe78349651c5edc634dc59e5a5f28a3e Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Oct 2024 12:59:59 +0300 Subject: [PATCH 24/44] fix docs --- rust/catalyst-voting/src/crypto/elgamal/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/catalyst-voting/src/crypto/elgamal/mod.rs b/rust/catalyst-voting/src/crypto/elgamal/mod.rs index 01aa76529d4..25b1920284c 100644 --- a/rust/catalyst-voting/src/crypto/elgamal/mod.rs +++ b/rust/catalyst-voting/src/crypto/elgamal/mod.rs @@ -1,4 +1,4 @@ -//! 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; @@ -7,7 +7,7 @@ use std::ops::{Add, Mul}; use crate::crypto::group::{GroupElement, Scalar}; -/// ``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); @@ -30,13 +30,13 @@ impl Ciphertext { } } -/// Generate ``ElGamal`` public key from the secret key value. +/// 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: &GroupElement, randomness: &Scalar) -> Ciphertext { let e1 = GroupElement::GENERATOR.mul(randomness); @@ -44,7 +44,7 @@ pub fn encrypt(message: &Scalar, public_key: &GroupElement, randomness: &Scalar) 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: &Scalar) -> GroupElement { &(&cipher.0 * &secret_key.negate()) + &cipher.1 From 7b17441d58b6fe63c4e5ef0481efc04fa050644c Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Oct 2024 13:22:34 +0300 Subject: [PATCH 25/44] refactor digest crate imports --- rust/catalyst-voting/Cargo.toml | 1 + rust/catalyst-voting/src/crypto/ed25519.rs | 9 +++++++++ .../src/crypto/group/ristretto255/mod.rs | 11 ++++++----- rust/catalyst-voting/src/crypto/hash.rs | 3 ++- rust/catalyst-voting/src/crypto/mod.rs | 1 + rust/catalyst-voting/src/crypto/zk_dl_equality.rs | 4 +--- .../src/crypto/zk_unit_vector/challenges.rs | 6 +++--- rust/catalyst-voting/src/txs/v1/mod.rs | 5 ++--- rust/catalyst-voting/src/vote_protocol/voter/proof.rs | 2 +- 9 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 rust/catalyst-voting/src/crypto/ed25519.rs diff --git a/rust/catalyst-voting/Cargo.toml b/rust/catalyst-voting/Cargo.toml index 7d250e5f949..a312cfb8e76 100644 --- a/rust/catalyst-voting/Cargo.toml +++ b/rust/catalyst-voting/Cargo.toml @@ -15,6 +15,7 @@ anyhow = "1.0.89" rand_core = "0.6.4" rand_chacha = "0.3.1" curve25519-dalek = { version = "4.1.3", features = ["digest"] } +ed25519-dalek = "2.1.1" blake2b_simd = "1.0.2" [dev-dependencies] diff --git a/rust/catalyst-voting/src/crypto/ed25519.rs b/rust/catalyst-voting/src/crypto/ed25519.rs new file mode 100644 index 00000000000..a2eabf8c4b7 --- /dev/null +++ b/rust/catalyst-voting/src/crypto/ed25519.rs @@ -0,0 +1,9 @@ +//! `EdDSA` digital signature scheme over Curve25519. + +// use ed25519_dalek::{ +// ed25519::signature::Signer, +// pkcs8::{DecodePrivateKey, DecodePublicKey}, +// SigningKey, VerifyingKey, +// }; + +// pub struct PrivateKey(SigningKey); diff --git a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs index 103f9a4fd0f..1a1ba5c1b54 100644 --- a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs +++ b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs @@ -11,13 +11,14 @@ 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] @@ -26,7 +27,7 @@ pub struct Scalar(IScalar); /// Ristretto group element. #[derive(Debug, Clone, PartialEq, Eq)] #[must_use] -pub struct GroupElement(Point); +pub struct GroupElement(RistrettoPoint); impl From for Scalar { fn from(value: u64) -> Self { @@ -86,13 +87,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(Point::from_hash(hash)) + GroupElement(RistrettoPoint::from_hash(hash)) } } diff --git a/rust/catalyst-voting/src/crypto/hash.rs b/rust/catalyst-voting/src/crypto/hash.rs index b8d5b64be34..2d821299078 100644 --- a/rust/catalyst-voting/src/crypto/hash.rs +++ b/rust/catalyst-voting/src/crypto/hash.rs @@ -1,6 +1,7 @@ //! Blake2b-256 hash implementation. -use curve25519_dalek::digest::{ +pub use curve25519_dalek::digest; +use digest::{ consts::U64, typenum::Unsigned, FixedOutput, HashMarker, Output, OutputSizeUser, Update, }; diff --git a/rust/catalyst-voting/src/crypto/mod.rs b/rust/catalyst-voting/src/crypto/mod.rs index 32efae43415..fb69c900e1a 100644 --- a/rust/catalyst-voting/src/crypto/mod.rs +++ b/rust/catalyst-voting/src/crypto/mod.rs @@ -1,6 +1,7 @@ //! Crypto primitives which are used by voting protocol. pub mod babystep_giantstep; +pub mod ed25519; pub mod elgamal; pub mod group; pub mod hash; diff --git a/rust/catalyst-voting/src/crypto/zk_dl_equality.rs b/rust/catalyst-voting/src/crypto/zk_dl_equality.rs index 9e1d4d75277..b3884458147 100644 --- a/rust/catalyst-voting/src/crypto/zk_dl_equality.rs +++ b/rust/catalyst-voting/src/crypto/zk_dl_equality.rs @@ -12,11 +12,9 @@ // cspell: words NIZK dlog -use curve25519_dalek::digest::Digest; - use crate::crypto::{ group::{GroupElement, Scalar}, - hash::Blake2b512Hasher, + hash::{digest::Digest, Blake2b512Hasher}, }; /// DLEQ proof struct 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 3237d41ff82..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,9 +1,9 @@ //! ZK unit vector challenges calculation functionality -use curve25519_dalek::digest::Digest; - use crate::crypto::{ - elgamal::Ciphertext, group::GroupElement, hash::Blake2b512Hasher, + elgamal::Ciphertext, + group::GroupElement, + hash::{digest::Digest, Blake2b512Hasher}, zk_unit_vector::randomness_announcements::Announcement, }; diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index 6079103c42a..1b9e5958461 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -2,12 +2,11 @@ mod decoding; -use curve25519_dalek::digest::Update; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; use crate::{ - crypto::hash::Blake2b512Hasher, + crypto::hash::{digest::Digest, Blake2b512Hasher}, vote_protocol::{ committee::ElectionPublicKey, voter::{ @@ -65,7 +64,7 @@ impl Tx { let mut rng = ChaCha20Rng::from_entropy(); let (encrypted_vote, randomness) = encrypt_vote(&vote, election_public_key, &mut rng); - let vote_plan_id_hash = Blake2b512Hasher::new().chain(vote_plan_id); + 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( diff --git a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs index f72f28fbee8..0c039532c8b 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs @@ -4,13 +4,13 @@ use std::ops::Mul; use anyhow::ensure; -use curve25519_dalek::digest::{consts::U64, Digest}; use rand_core::CryptoRngCore; use super::{EncryptedVote, EncryptionRandomness, Vote}; use crate::{ crypto::{ group::{GroupElement, Scalar}, + hash::digest::{consts::U64, Digest}, zk_unit_vector::{generate_unit_vector_proof, verify_unit_vector_proof, UnitVectorProof}, }, vote_protocol::committee::ElectionPublicKey, From 8873b9b5a8e6f83581b6e3eeb0b8a253dd19817c Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Oct 2024 14:55:48 +0300 Subject: [PATCH 26/44] add ed25519 impl --- rust/catalyst-voting/Cargo.toml | 2 +- rust/catalyst-voting/src/crypto/ed25519.rs | 73 ++++++++++++++++++++-- 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/rust/catalyst-voting/Cargo.toml b/rust/catalyst-voting/Cargo.toml index a312cfb8e76..39357149918 100644 --- a/rust/catalyst-voting/Cargo.toml +++ b/rust/catalyst-voting/Cargo.toml @@ -15,7 +15,7 @@ anyhow = "1.0.89" rand_core = "0.6.4" rand_chacha = "0.3.1" curve25519-dalek = { version = "4.1.3", features = ["digest"] } -ed25519-dalek = "2.1.1" +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.rs b/rust/catalyst-voting/src/crypto/ed25519.rs index a2eabf8c4b7..18af74ddfe2 100644 --- a/rust/catalyst-voting/src/crypto/ed25519.rs +++ b/rust/catalyst-voting/src/crypto/ed25519.rs @@ -1,9 +1,70 @@ //! `EdDSA` digital signature scheme over Curve25519. -// use ed25519_dalek::{ -// ed25519::signature::Signer, -// pkcs8::{DecodePrivateKey, DecodePublicKey}, -// SigningKey, VerifyingKey, -// }; +use ed25519_dalek::{ + ed25519::signature::Signer, Signature as Ed25519Signature, SigningKey, VerifyingKey, +}; +use rand_core::CryptoRngCore; -// pub struct PrivateKey(SigningKey); +/// `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)); + } +} From f2c95cc423fc70ca4256d05161c62b11e93a9cea Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Oct 2024 15:11:03 +0300 Subject: [PATCH 27/44] add ed25519 decoding functionality --- .../src/crypto/ed25519/decoding.rs | 39 +++++++++++++++++++ .../src/crypto/{ed25519.rs => ed25519/mod.rs} | 2 + 2 files changed, 41 insertions(+) create mode 100644 rust/catalyst-voting/src/crypto/ed25519/decoding.rs rename rust/catalyst-voting/src/crypto/{ed25519.rs => ed25519/mod.rs} (99%) 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..3b5cfdb5b1a --- /dev/null +++ b/rust/catalyst-voting/src/crypto/ed25519/decoding.rs @@ -0,0 +1,39 @@ +//! `Ed25519` objects decoding implementation + +use ed25519_dalek::{VerifyingKey, PUBLIC_KEY_LENGTH}; + +use super::PublicKey; + +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)?)) + } +} + +#[cfg(test)] +mod tests { + use test_strategy::proptest; + + use super::{super::PrivateKey, *}; + + #[proptest] + fn public_key_to_bytes_from_bytes_test(private_key: PrivateKey) { + let public_key = private_key.public_key(); + let public_key_bytes = public_key.to_bytes(); + let public_key2 = PublicKey::from_bytes(&public_key_bytes).unwrap(); + assert_eq!(public_key, public_key2); + } +} diff --git a/rust/catalyst-voting/src/crypto/ed25519.rs b/rust/catalyst-voting/src/crypto/ed25519/mod.rs similarity index 99% rename from rust/catalyst-voting/src/crypto/ed25519.rs rename to rust/catalyst-voting/src/crypto/ed25519/mod.rs index 18af74ddfe2..a9cc4488e54 100644 --- a/rust/catalyst-voting/src/crypto/ed25519.rs +++ b/rust/catalyst-voting/src/crypto/ed25519/mod.rs @@ -1,5 +1,7 @@ //! `EdDSA` digital signature scheme over Curve25519. +mod decoding; + use ed25519_dalek::{ ed25519::signature::Signer, Signature as Ed25519Signature, SigningKey, VerifyingKey, }; From b0b94aab06085a3f31e909b6fcf5f3539545a833 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Oct 2024 15:14:48 +0300 Subject: [PATCH 28/44] update v1::Tx --- rust/catalyst-voting/src/txs/v1/decoding.rs | 14 +++++++---- rust/catalyst-voting/src/txs/v1/mod.rs | 28 ++++++++++++++------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/rust/catalyst-voting/src/txs/v1/decoding.rs b/rust/catalyst-voting/src/txs/v1/decoding.rs index 127a4ef0bc2..1e35d37eaf9 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -5,7 +5,10 @@ use std::io::Read; use anyhow::{anyhow, bail, ensure}; use super::{EncryptedVote, Tx, VotePayload, VoterProof}; -use crate::utils::{read_array, read_be_u32, read_be_u64, read_be_u8}; +use crate::{ + crypto::ed25519::PublicKey, + utils::{read_array, read_be_u32, read_be_u64, read_be_u8}, +}; impl Tx { /// Convert this `Tx` to its underlying sequence of bytes. @@ -46,7 +49,7 @@ impl Tx { // Zero value tx_body.extend_from_slice(&[0u8; 8]); - // tx_body.extend_from_slice(&self.public_key.to_bytes()); + tx_body.extend_from_slice(&self.public_key.to_bytes()); // Add the size of decoded bytes to the beginning. let mut res = (tx_body.len() as u32).to_be_bytes().to_vec(); @@ -128,14 +131,15 @@ impl Tx { // skip value read_be_u64(reader)?; - // let public_key_bytes = read_array(reader)?; - // let public_key = PublicKey::from_bytes(&public_key_bytes) - // .map_err(|e| anyhow!("Invalid public key, error: {e}."))?; + let public_key_bytes = read_array(reader)?; + let public_key = PublicKey::from_bytes(&public_key_bytes) + .map_err(|e| anyhow!("Invalid public key, error: {e}."))?; Ok(Self { vote_plan_id, proposal_index, vote, + public_key, }) } } diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index 1b9e5958461..1b432ae172a 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -6,7 +6,10 @@ use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; use crate::{ - crypto::hash::{digest::Digest, Blake2b512Hasher}, + crypto::{ + ed25519::PublicKey, + hash::{digest::Digest, Blake2b512Hasher}, + }, vote_protocol::{ committee::ElectionPublicKey, voter::{ @@ -27,8 +30,8 @@ pub struct Tx { proposal_index: u8, /// Vote vote: VotePayload, - // /// Public key - // public_key: PublicKey, + /// Public key + public_key: PublicKey, } /// Vote payload struct. @@ -43,11 +46,14 @@ pub enum VotePayload { impl Tx { /// Generate a new `Tx` with public vote - pub fn new_public(vote_plan_id: [u8; 32], proposal_index: u8, choice: u8) -> Self { + pub fn new_public( + vote_plan_id: [u8; 32], proposal_index: u8, choice: u8, public_key: PublicKey, + ) -> Self { Self { vote_plan_id, proposal_index, vote: VotePayload::Public(choice), + public_key, } } @@ -57,7 +63,7 @@ impl Tx { /// - Invalid voting choice pub fn new_private( vote_plan_id: [u8; 32], proposal_index: u8, proposal_voting_options: u8, choice: u8, - election_public_key: &ElectionPublicKey, + election_public_key: &ElectionPublicKey, users_public_key: PublicKey, ) -> anyhow::Result { let vote = Vote::new(choice.into(), proposal_voting_options.into())?; @@ -80,6 +86,7 @@ impl Tx { vote_plan_id, proposal_index, vote: VotePayload::Private(encrypted_vote, voter_proof), + public_key: users_public_key, }) } } @@ -90,19 +97,20 @@ mod tests { use test_strategy::proptest; use super::*; - use crate::vote_protocol::committee::ElectionSecretKey; + use crate::{crypto::ed25519::PrivateKey, vote_protocol::committee::ElectionSecretKey}; impl Arbitrary for Tx { type Parameters = (); type Strategy = BoxedStrategy; fn arbitrary_with((): Self::Parameters) -> Self::Strategy { - any::<([u8; 32], u8, VotePayload)>() - .prop_map(|(vote_plan_id, proposal_index, vote)| { + any::<([u8; 32], u8, VotePayload, PrivateKey)>() + .prop_map(|(vote_plan_id, proposal_index, vote, sk)| { Tx { vote_plan_id, proposal_index, vote, + public_key: sk.public_key(), } }) .boxed() @@ -134,9 +142,10 @@ mod tests { #[proptest] fn tx_private_test( vote_plan_id: [u8; 32], proposal_index: u8, #[strategy(1u8..)] proposal_voting_options: u8, - #[strategy(0..#proposal_voting_options)] choice: u8, + #[strategy(0..#proposal_voting_options)] choice: u8, users_private_key: PrivateKey, election_secret_key: ElectionSecretKey, ) { + let users_public_key = users_private_key.public_key(); let election_public_key = election_secret_key.public_key(); let _tx = Tx::new_private( @@ -145,6 +154,7 @@ mod tests { proposal_voting_options, choice, &election_public_key, + users_public_key, ) .unwrap(); } From a81277ca6ac1c60c9d7619ec0f141f93aab6c2f4 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Oct 2024 16:28:30 +0300 Subject: [PATCH 29/44] add Tx signing --- .../src/crypto/ed25519/decoding.rs | 27 ++-- rust/catalyst-voting/src/txs/v1/decoding.rs | 121 ++++++++++++--- rust/catalyst-voting/src/txs/v1/mod.rs | 141 ++++++++++-------- 3 files changed, 188 insertions(+), 101 deletions(-) diff --git a/rust/catalyst-voting/src/crypto/ed25519/decoding.rs b/rust/catalyst-voting/src/crypto/ed25519/decoding.rs index 3b5cfdb5b1a..ab4b5daa723 100644 --- a/rust/catalyst-voting/src/crypto/ed25519/decoding.rs +++ b/rust/catalyst-voting/src/crypto/ed25519/decoding.rs @@ -1,8 +1,10 @@ //! `Ed25519` objects decoding implementation -use ed25519_dalek::{VerifyingKey, PUBLIC_KEY_LENGTH}; +use ed25519_dalek::{ + Signature as Ed25519Signature, VerifyingKey, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, +}; -use super::PublicKey; +use super::{PublicKey, Signature}; impl PublicKey { /// `PublicKey` bytes size @@ -23,17 +25,18 @@ impl PublicKey { } } -#[cfg(test)] -mod tests { - use test_strategy::proptest; +impl Signature { + /// `Signature` bytes size + pub const BYTES_SIZE: usize = SIGNATURE_LENGTH; - use super::{super::PrivateKey, *}; + /// Convert this `Signature` to its underlying sequence of bytes. + #[must_use] + pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] { + self.0.to_bytes() + } - #[proptest] - fn public_key_to_bytes_from_bytes_test(private_key: PrivateKey) { - let public_key = private_key.public_key(); - let public_key_bytes = public_key.to_bytes(); - let public_key2 = PublicKey::from_bytes(&public_key_bytes).unwrap(); - assert_eq!(public_key, public_key2); + /// 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/txs/v1/decoding.rs b/rust/catalyst-voting/src/txs/v1/decoding.rs index 1e35d37eaf9..adc3990d69d 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -6,50 +6,67 @@ use anyhow::{anyhow, bail, ensure}; use super::{EncryptedVote, Tx, VotePayload, VoterProof}; use crate::{ - crypto::ed25519::PublicKey, + crypto::ed25519::{PublicKey, Signature}, utils::{read_array, read_be_u32, read_be_u64, read_be_u8}, }; impl Tx { - /// Convert this `Tx` to its underlying sequence of bytes. - #[must_use] + /// Write the bytes to sign for the `Tx` 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![0, 11]; - - tx_body.extend_from_slice(&self.vote_plan_id); - tx_body.push(self.proposal_index); - - match &self.vote { + pub(super) fn bytes_to_sign( + 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); + + match vote { VotePayload::Public(vote) => { // Public vote tag - tx_body.push(1); - tx_body.push(*vote); + buf.push(1); + buf.push(*vote); }, VotePayload::Private(vote, proof) => { // Private vote tag - tx_body.push(2); - tx_body.push(vote.size() as u8); - tx_body.extend_from_slice(&vote.to_bytes()); + buf.push(2); + 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(1); + buf.push(1); // Number of outputs - tx_body.push(0); + buf.push(0); // Input tag - tx_body.push(0xFF); + buf.push(0xFF); // Zero value - tx_body.extend_from_slice(&[0u8; 8]); + buf.extend_from_slice(&[0u8; 8]); + + 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 tx_body = vec![0, 11]; + + Self::bytes_to_sign( + &self.vote_plan_id, + self.proposal_index, + &self.vote, + &self.public_key, + &mut tx_body, + ); - tx_body.extend_from_slice(&self.public_key.to_bytes()); + tx_body.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(); @@ -135,20 +152,76 @@ impl Tx { let public_key = PublicKey::from_bytes(&public_key_bytes) .map_err(|e| anyhow!("Invalid public key, error: {e}."))?; + 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)] diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index 1b432ae172a..55ba9e005de 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -7,7 +7,7 @@ use rand_core::SeedableRng; use crate::{ crypto::{ - ed25519::PublicKey, + ed25519::{sign, PrivateKey, PublicKey, Signature}, hash::{digest::Digest, Blake2b512Hasher}, }, vote_protocol::{ @@ -32,6 +32,8 @@ pub struct Tx { vote: VotePayload, /// Public key public_key: PublicKey, + /// Transaction signature + signature: Signature, } /// Vote payload struct. @@ -44,26 +46,18 @@ pub enum VotePayload { Private(EncryptedVote, VoterProof), } -impl Tx { - /// Generate a new `Tx` with public vote - pub fn new_public( - vote_plan_id: [u8; 32], proposal_index: u8, choice: u8, public_key: PublicKey, - ) -> Self { - Self { - vote_plan_id, - proposal_index, - vote: VotePayload::Public(choice), - public_key, - } +#[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)) } - /// Generate a new `Tx` with public vote - /// - /// # Errors - /// - Invalid voting choice - pub fn new_private( - vote_plan_id: [u8; 32], proposal_index: u8, proposal_voting_options: u8, choice: u8, - election_public_key: &ElectionPublicKey, users_public_key: PublicKey, + fn new_private( + vote_plan_id: &[u8; 32], choice: u8, proposal_voting_options: u8, + election_public_key: &ElectionPublicKey, ) -> anyhow::Result { let vote = Vote::new(choice.into(), proposal_voting_options.into())?; @@ -82,79 +76,96 @@ impl Tx { &mut rng, )?; + Ok(Self::Private(encrypted_vote, voter_proof)) + } +} + +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, choice: u8, proposal_voting_options: u8, + users_private_key: &PrivateKey, + ) -> anyhow::Result { + let vote = VotePayload::new_public(choice, proposal_voting_options)?; + let signature = Self::sign(&vote_plan_id, proposal_index, &vote, users_private_key); Ok(Self { vote_plan_id, proposal_index, - vote: VotePayload::Private(encrypted_vote, voter_proof), - public_key: users_public_key, + vote, + public_key: users_private_key.public_key(), + signature, }) } + + /// Generate a new `Tx` with public vote + /// + /// # Errors + /// - Invalid voting choice + pub fn new_private( + vote_plan_id: [u8; 32], proposal_index: u8, choice: u8, proposal_voting_options: u8, + election_public_key: &ElectionPublicKey, users_private_key: &PrivateKey, + ) -> anyhow::Result { + let vote = VotePayload::new_private( + &vote_plan_id, + choice, + proposal_voting_options, + election_public_key, + )?; + 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 transaction signature + fn sign( + vote_plan_id: &[u8; 32], proposal_index: u8, vote: &VotePayload, private_key: &PrivateKey, + ) -> Signature { + let mut bytes = Vec::new(); + Self::bytes_to_sign( + vote_plan_id, + proposal_index, + vote, + &private_key.public_key(), + &mut bytes, + ); + let msg = Blake2b512Hasher::new() + .chain_update(bytes.as_slice()) + .finalize(); + sign(private_key, msg.as_slice()) + } } #[cfg(test)] mod tests { - use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, Strategy}; use test_strategy::proptest; use super::*; use crate::{crypto::ed25519::PrivateKey, vote_protocol::committee::ElectionSecretKey}; - impl Arbitrary for Tx { - type Parameters = (); - type Strategy = BoxedStrategy; - - fn arbitrary_with((): Self::Parameters) -> Self::Strategy { - any::<([u8; 32], u8, VotePayload, PrivateKey)>() - .prop_map(|(vote_plan_id, proposal_index, vote, sk)| { - Tx { - vote_plan_id, - proposal_index, - vote, - public_key: sk.public_key(), - } - }) - .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] fn tx_private_test( vote_plan_id: [u8; 32], proposal_index: u8, #[strategy(1u8..)] proposal_voting_options: u8, #[strategy(0..#proposal_voting_options)] choice: u8, users_private_key: PrivateKey, election_secret_key: ElectionSecretKey, ) { - let users_public_key = users_private_key.public_key(); let election_public_key = election_secret_key.public_key(); let _tx = Tx::new_private( vote_plan_id, proposal_index, - proposal_voting_options, choice, + proposal_voting_options, &election_public_key, - users_public_key, + &users_private_key, ) .unwrap(); } From 96f1114a28b737dedc1319793d5c40abfce298f8 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Oct 2024 16:30:35 +0300 Subject: [PATCH 30/44] wip --- rust/catalyst-voting/src/txs/v1/mod.rs | 68 +++++++++++++------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index 55ba9e005de..d061f227279 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -46,40 +46,6 @@ pub enum VotePayload { Private(EncryptedVote, VoterProof), } -#[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, - ) -> anyhow::Result { - let vote = Vote::new(choice.into(), proposal_voting_options.into())?; - - let mut rng = ChaCha20Rng::from_entropy(); - let (encrypted_vote, randomness) = encrypt_vote(&vote, election_public_key, &mut rng); - - 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, - &mut rng, - )?; - - Ok(Self::Private(encrypted_vote, voter_proof)) - } -} - impl Tx { /// Generate a new `Tx` with public vote /// @@ -144,6 +110,40 @@ impl Tx { } } +#[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, + ) -> anyhow::Result { + let vote = Vote::new(choice.into(), proposal_voting_options.into())?; + + let mut rng = ChaCha20Rng::from_entropy(); + let (encrypted_vote, randomness) = encrypt_vote(&vote, election_public_key, &mut rng); + + 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, + &mut rng, + )?; + + Ok(Self::Private(encrypted_vote, voter_proof)) + } +} + #[cfg(test)] mod tests { use test_strategy::proptest; From f2e657a4e3a3ccaaee8a32d9bd7f3bb17a8f0423 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Oct 2024 17:38:50 +0300 Subject: [PATCH 31/44] fix --- .../catalyst-voting/src/crypto/elgamal/mod.rs | 11 ---- .../src/crypto/zk_unit_vector/mod.rs | 18 ------- rust/catalyst-voting/src/txs/v1/decoding.rs | 53 ++++++------------- 3 files changed, 15 insertions(+), 67 deletions(-) diff --git a/rust/catalyst-voting/src/crypto/elgamal/mod.rs b/rust/catalyst-voting/src/crypto/elgamal/mod.rs index e16666cabf3..25b1920284c 100644 --- a/rust/catalyst-voting/src/crypto/elgamal/mod.rs +++ b/rust/catalyst-voting/src/crypto/elgamal/mod.rs @@ -87,17 +87,6 @@ mod tests { } } - impl Arbitrary for Ciphertext { - type Parameters = (); - type Strategy = BoxedStrategy; - - fn arbitrary_with((): Self::Parameters) -> Self::Strategy { - any::<(GroupElement, GroupElement)>() - .prop_map(|(g1, g2)| Ciphertext(g1, g2)) - .boxed() - } - } - #[proptest] fn ciphertext_add_test(e1: Scalar, e2: Scalar, e3: Scalar, e4: Scalar) { let g1 = GroupElement::GENERATOR.mul(&e1); 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 792c0080c00..1e85ad2dd79 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs @@ -263,24 +263,6 @@ mod tests { } } - impl Arbitrary for UnitVectorProof { - type Parameters = usize; - type Strategy = BoxedStrategy; - - fn arbitrary_with(size: Self::Parameters) -> Self::Strategy { - any_with::<( - Vec<((Announcement, Ciphertext), ResponseRandomness)>, - Scalar, - )>(((size_range(size), (((), ()), ())), ())) - .prop_map(|(val, scalar)| { - let (vec, rr): (Vec<_>, Vec<_>) = val.into_iter().unzip(); - let (an, cipher) = vec.into_iter().unzip(); - Self(an, cipher, rr, scalar) - }) - .boxed() - } - } - fn is_unit_vector(vector: &[Scalar]) -> bool { let ones = vector.iter().filter(|s| s == &&Scalar::one()).count(); let zeros = vector.iter().filter(|s| s == &&Scalar::zero()).count(); diff --git a/rust/catalyst-voting/src/txs/v1/decoding.rs b/rust/catalyst-voting/src/txs/v1/decoding.rs index 71784eab162..b517876cfdf 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -38,12 +38,12 @@ impl Tx { match vote { VotePayload::Public(vote) => { // Public vote tag - buf.push(1); + buf.push(PUBLIC_VOTE_TAG); buf.push(*vote); }, VotePayload::Private(vote, proof) => { // Private vote tag - buf.push(2); + buf.push(PRIVATE_VOTE_TAG); buf.push(vote.size() as u8); buf.extend_from_slice(&vote.to_bytes()); @@ -55,11 +55,11 @@ impl Tx { // Zeros block date buf.extend_from_slice(&[0u8; 8]); // Number of inputs - buf.push(1); + buf.push(NUMBER_OF_INPUTS); // Number of outputs - buf.push(0); + buf.push(NUMBER_OF_OUTPUTS); // Input tag - buf.push(0xFF); + buf.push(INPUT_TAG); // Zero value buf.extend_from_slice(&[0u8; 8]); @@ -73,38 +73,15 @@ impl Tx { // Initialize already with the padding tag `0` and fragment tag `11`. let mut tx_body = vec![PADDING_TAG, FRAGMENT_TAG]; - tx_body.extend_from_slice(&self.vote_plan_id); - tx_body.push(self.proposal_index); - - match &self.vote { - Vote::Public(vote) => { - // Public vote tag - tx_body.push(PUBLIC_VOTE_TAG); - tx_body.push(*vote); - }, - Vote::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()); - - tx_body.push(proof.size() as u8); - tx_body.extend_from_slice(&proof.to_bytes()); - }, - } - - // Zeros block date - tx_body.extend_from_slice(&[0u8; 8]); - // Number of inputs - tx_body.push(NUMBER_OF_INPUTS); - // Number of outputs - tx_body.push(NUMBER_OF_OUTPUTS); - // Input tag - tx_body.push(INPUT_TAG); - // Zero value - tx_body.extend_from_slice(&[0u8; 8]); + Self::bytes_to_sign( + &self.vote_plan_id, + self.proposal_index, + &self.vote, + &self.public_key, + &mut tx_body, + ); - tx_body.extend_from_slice(&self.public_key.to_bytes()); + tx_body.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(); @@ -146,7 +123,7 @@ impl Tx { let vote = match vote_tag { PUBLIC_VOTE_TAG => { let vote = read_be_u8(reader)?; - Vote::Public(vote) + VotePayload::Public(vote) }, PRIVATE_VOTE_TAG => { let size = read_be_u8(reader)?; @@ -157,7 +134,7 @@ impl Tx { 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!( From 0b2ac1eb697a439740c361ffbc9c8754a4a06914 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Oct 2024 17:45:17 +0300 Subject: [PATCH 32/44] wip --- rust/catalyst-voting/src/txs/v1/mod.rs | 66 ++++++++++++++++---------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index d061f227279..ce119a78752 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -46,20 +46,33 @@ pub enum VotePayload { Private(EncryptedVote, VoterProof), } +/// Proposal information struct. +#[derive(Debug, Clone, PartialEq, Eq)] +#[allow(clippy::missing_docs_in_private_items)] +pub struct ProposalInfo { + vote_plan_id: [u8; 32], + proposal_index: u8, + voting_options: u8, +} + 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, choice: u8, proposal_voting_options: u8, - users_private_key: &PrivateKey, + proposal_info: &ProposalInfo, choice: u8, users_private_key: &PrivateKey, ) -> anyhow::Result { - let vote = VotePayload::new_public(choice, proposal_voting_options)?; - let signature = Self::sign(&vote_plan_id, proposal_index, &vote, users_private_key); + let vote = VotePayload::new_public(choice, proposal_info.voting_options)?; + let signature = Self::sign( + &proposal_info.vote_plan_id, + proposal_info.proposal_index, + &vote, + users_private_key, + ); Ok(Self { - vote_plan_id, - proposal_index, + vote_plan_id: proposal_info.vote_plan_id, + proposal_index: proposal_info.proposal_index, vote, public_key: users_private_key.public_key(), signature, @@ -71,20 +84,25 @@ impl Tx { /// # Errors /// - Invalid voting choice pub fn new_private( - vote_plan_id: [u8; 32], proposal_index: u8, choice: u8, proposal_voting_options: u8, - election_public_key: &ElectionPublicKey, users_private_key: &PrivateKey, + proposal_info: &ProposalInfo, choice: u8, election_public_key: &ElectionPublicKey, + users_private_key: &PrivateKey, ) -> anyhow::Result { let vote = VotePayload::new_private( - &vote_plan_id, + &proposal_info.vote_plan_id, choice, - proposal_voting_options, + proposal_info.voting_options, election_public_key, )?; - let signature = Self::sign(&vote_plan_id, proposal_index, &vote, users_private_key); + let signature = Self::sign( + &proposal_info.vote_plan_id, + proposal_info.proposal_index, + &vote, + users_private_key, + ); Ok(Self { - vote_plan_id, - proposal_index, + vote_plan_id: proposal_info.vote_plan_id, + proposal_index: proposal_info.proposal_index, vote, public_key: users_private_key.public_key(), signature, @@ -152,21 +170,21 @@ mod tests { use crate::{crypto::ed25519::PrivateKey, vote_protocol::committee::ElectionSecretKey}; #[proptest] - fn tx_private_test( - vote_plan_id: [u8; 32], proposal_index: u8, #[strategy(1u8..)] proposal_voting_options: u8, - #[strategy(0..#proposal_voting_options)] choice: u8, users_private_key: PrivateKey, + fn tx_test( + vote_plan_id: [u8; 32], proposal_index: u8, #[strategy(1u8..)] 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_private( + let proposal = ProposalInfo { vote_plan_id, proposal_index, - choice, - proposal_voting_options, - &election_public_key, - &users_private_key, - ) - .unwrap(); + voting_options, + }; + + let _tx = Tx::new_public(&proposal, choice, &users_private_key).unwrap(); + + let _tx = + Tx::new_private(&proposal, choice, &election_public_key, &users_private_key).unwrap(); } } From 513965cbb2a0fdc424c82bc546d2100427232958 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Oct 2024 21:09:20 +0300 Subject: [PATCH 33/44] add Blake2b-256 hash impl, update v1::Tx sign --- rust/catalyst-voting/src/crypto/hash.rs | 51 ++++++++++++++++++++++++- rust/catalyst-voting/src/txs/v1/mod.rs | 4 +- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/rust/catalyst-voting/src/crypto/hash.rs b/rust/catalyst-voting/src/crypto/hash.rs index 2d821299078..bfade141410 100644 --- a/rust/catalyst-voting/src/crypto/hash.rs +++ b/rust/catalyst-voting/src/crypto/hash.rs @@ -2,7 +2,9 @@ pub use curve25519_dalek::digest; use digest::{ - consts::U64, typenum::Unsigned, FixedOutput, HashMarker, Output, OutputSizeUser, Update, + consts::{U32, U64}, + typenum::Unsigned, + FixedOutput, HashMarker, Output, OutputSizeUser, Update, }; /// Blake2b-512 hasher instance. @@ -51,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/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index ce119a78752..08c0b60d20f 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -8,7 +8,7 @@ use rand_core::SeedableRng; use crate::{ crypto::{ ed25519::{sign, PrivateKey, PublicKey, Signature}, - hash::{digest::Digest, Blake2b512Hasher}, + hash::{digest::Digest, Blake2b256Hasher, Blake2b512Hasher}, }, vote_protocol::{ committee::ElectionPublicKey, @@ -121,7 +121,7 @@ impl Tx { &private_key.public_key(), &mut bytes, ); - let msg = Blake2b512Hasher::new() + let msg = Blake2b256Hasher::new() .chain_update(bytes.as_slice()) .finalize(); sign(private_key, msg.as_slice()) From d1820eff465c87cdd75924bc0827a7deef2691df Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Oct 2024 21:24:20 +0300 Subject: [PATCH 34/44] add txs::v1 doc test --- rust/catalyst-voting/src/txs/v1/mod.rs | 105 +++++++++++++++---------- 1 file changed, 64 insertions(+), 41 deletions(-) diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index 08c0b60d20f..9a51d10434d 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -1,4 +1,40 @@ //! 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) +//! +//! ```rust +//! use catalyst_voting::{ +//! crypto::ed25519::PrivateKey, txs::v1::Tx, vote_protocol::committee::ElectionSecretKey, +//! }; +//! +//! let mut rng = rand_core::OsRng; +//! +//! let vote_plan_id = [0u8; 32]; +//! let proposal_index = 0u8; +//! +//! let voting_options = 3; +//! let choice = 1; +//! +//! let users_private_key = PrivateKey::random(&mut rng); +//! let election_public_key = ElectionSecretKey::random(&mut rng).public_key(); +//! +//! let public_tx = Tx::new_public( +//! vote_plan_id, +//! proposal_index, +//! voting_options, +//! choice, +//! &users_private_key, +//! ) +//! .unwrap(); +//! +//! let private_tx = Tx::new_private( +//! vote_plan_id, +//! proposal_index, +//! voting_options, +//! choice, +//! &election_public_key, +//! &users_private_key, +//! ) +//! .unwrap(); +//! ``` mod decoding; @@ -46,33 +82,20 @@ pub enum VotePayload { Private(EncryptedVote, VoterProof), } -/// Proposal information struct. -#[derive(Debug, Clone, PartialEq, Eq)] -#[allow(clippy::missing_docs_in_private_items)] -pub struct ProposalInfo { - vote_plan_id: [u8; 32], - proposal_index: u8, - voting_options: u8, -} - impl Tx { /// Generate a new `Tx` with public vote /// /// # Errors /// - Invalid voting choice pub fn new_public( - proposal_info: &ProposalInfo, choice: u8, users_private_key: &PrivateKey, + 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, proposal_info.voting_options)?; - let signature = Self::sign( - &proposal_info.vote_plan_id, - proposal_info.proposal_index, - &vote, - users_private_key, - ); + 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_info.vote_plan_id, - proposal_index: proposal_info.proposal_index, + vote_plan_id, + proposal_index, vote, public_key: users_private_key.public_key(), signature, @@ -84,25 +107,16 @@ impl Tx { /// # Errors /// - Invalid voting choice pub fn new_private( - proposal_info: &ProposalInfo, choice: u8, election_public_key: &ElectionPublicKey, - users_private_key: &PrivateKey, + vote_plan_id: [u8; 32], proposal_index: u8, voting_options: u8, choice: u8, + election_public_key: &ElectionPublicKey, users_private_key: &PrivateKey, ) -> anyhow::Result { - let vote = VotePayload::new_private( - &proposal_info.vote_plan_id, - choice, - proposal_info.voting_options, - election_public_key, - )?; - let signature = Self::sign( - &proposal_info.vote_plan_id, - proposal_info.proposal_index, - &vote, - users_private_key, - ); + let vote = + VotePayload::new_private(&vote_plan_id, choice, voting_options, election_public_key)?; + let signature = Self::sign(&vote_plan_id, proposal_index, &vote, users_private_key); Ok(Self { - vote_plan_id: proposal_info.vote_plan_id, - proposal_index: proposal_info.proposal_index, + vote_plan_id, + proposal_index, vote, public_key: users_private_key.public_key(), signature, @@ -176,15 +190,24 @@ mod tests { election_secret_key: ElectionSecretKey, ) { let election_public_key = election_secret_key.public_key(); - let proposal = ProposalInfo { + + let _tx = Tx::new_public( vote_plan_id, proposal_index, voting_options, - }; - - let _tx = Tx::new_public(&proposal, choice, &users_private_key).unwrap(); + choice, + &users_private_key, + ) + .unwrap(); - let _tx = - Tx::new_private(&proposal, choice, &election_public_key, &users_private_key).unwrap(); + let _tx = Tx::new_private( + vote_plan_id, + proposal_index, + voting_options, + choice, + &election_public_key, + &users_private_key, + ) + .unwrap(); } } From 43fe42a3e2c325238f869cad0d8c00b6e8636690 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 15 Oct 2024 11:25:20 +0300 Subject: [PATCH 35/44] update rust docs --- rust/catalyst-voting/src/crypto/hash.rs | 2 +- rust/catalyst-voting/src/vote_protocol/mod.rs | 102 ++++++++++++++---- 2 files changed, 82 insertions(+), 22 deletions(-) diff --git a/rust/catalyst-voting/src/crypto/hash.rs b/rust/catalyst-voting/src/crypto/hash.rs index bfade141410..7ce3fe6f0b1 100644 --- a/rust/catalyst-voting/src/crypto/hash.rs +++ b/rust/catalyst-voting/src/crypto/hash.rs @@ -1,4 +1,4 @@ -//! Blake2b-256 hash implementation. +//! Different hash implementations. pub use curve25519_dalek::digest; use digest::{ diff --git a/rust/catalyst-voting/src/vote_protocol/mod.rs b/rust/catalyst-voting/src/vote_protocol/mod.rs index 3f26bbd07da..0d83a0e3bcb 100644 --- a/rust/catalyst-voting/src/vote_protocol/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/mod.rs @@ -8,7 +8,11 @@ //! proof::{generate_tally_proof, verify_tally_proof}, //! tally, DecryptionTallySetup, //! }, -//! voter::{encrypt_vote, Vote}, +//! voter::{ +//! encrypt_vote, +//! proof::{generate_voter_proof, verify_voter_proof, VoterProofCommitment}, +//! Vote, +//! }, //! }; //! //! struct Voter { @@ -16,10 +20,12 @@ //! choice: usize, //! } //! +//! // Initial setup //! let mut rng = rand_core::OsRng; //! let voting_options = 3; //! let election_secret_key = ElectionSecretKey::random(&mut rng); //! let election_public_key = election_secret_key.public_key(); +//! let voter_proof_commitment = VoterProofCommitment::random(&mut rng); //! //! let voter_1 = Voter { //! voting_power: 10, @@ -36,6 +42,7 @@ //! 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(); @@ -46,22 +53,67 @@ //! encrypt_vote(&vote_2, &election_public_key, &mut rng); //! 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]; //! -//! 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( +//! &vote_1, +//! encrypted_vote_1.clone(), +//! voter_randomness_1, +//! &election_public_key, +//! &voter_proof_commitment, +//! &mut rng, +//! ) +//! .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( +//! &vote_2, +//! encrypted_vote_2.clone(), +//! voter_randomness_2, +//! &election_public_key, +//! &voter_proof_commitment, +//! &mut rng, +//! ) +//! .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( +//! &vote_3, +//! encrypted_vote_3.clone(), +//! voter_randomness_3, +//! &election_public_key, +//! &voter_proof_commitment, +//! &mut rng, +//! ) +//! .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( @@ -73,12 +125,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(t, &election_secret_key, &mut rng)) +//! .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, From 0993cb0581b3eba15402ba3705cb399f1083f639 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 15 Oct 2024 12:07:18 +0300 Subject: [PATCH 36/44] make rng optional --- rust/catalyst-voting/Cargo.toml | 2 +- .../src/crypto/group/ristretto255/mod.rs | 4 +-- rust/catalyst-voting/src/crypto/mod.rs | 9 ++++++ rust/catalyst-voting/src/txs/v1/mod.rs | 14 ++++----- .../src/vote_protocol/committee.rs | 17 +++++------ rust/catalyst-voting/src/vote_protocol/mod.rs | 30 +++++++++---------- .../src/vote_protocol/tally/proof.rs | 10 +++++++ .../src/vote_protocol/voter/mod.rs | 15 ++++++++-- .../src/vote_protocol/voter/proof.rs | 27 +++++++++++++++++ rust/catalyst-voting/tests/voting_test.rs | 19 +++++------- 10 files changed, 96 insertions(+), 51 deletions(-) diff --git a/rust/catalyst-voting/Cargo.toml b/rust/catalyst-voting/Cargo.toml index 39357149918..ce8c106b76b 100644 --- a/rust/catalyst-voting/Cargo.toml +++ b/rust/catalyst-voting/Cargo.toml @@ -14,7 +14,7 @@ workspace = true anyhow = "1.0.89" rand_core = "0.6.4" rand_chacha = "0.3.1" -curve25519-dalek = { version = "4.1.3", features = ["digest"] } +curve25519-dalek = { version = "4.1.3", features = ["digest", "rand_core"] } ed25519-dalek = { version = "2.1.1", features = ["rand_core"] } blake2b_simd = "1.0.2" diff --git a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs index 1a1ba5c1b54..d291da41828 100644 --- a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs +++ b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs @@ -44,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 diff --git a/rust/catalyst-voting/src/crypto/mod.rs b/rust/catalyst-voting/src/crypto/mod.rs index fb69c900e1a..7ad5b43eb74 100644 --- a/rust/catalyst-voting/src/crypto/mod.rs +++ b/rust/catalyst-voting/src/crypto/mod.rs @@ -1,5 +1,8 @@ //! Crypto primitives which are used by voting protocol. +use rand_chacha::ChaCha8Rng; +use rand_core::{CryptoRngCore, SeedableRng}; + pub mod babystep_giantstep; pub mod ed25519; pub mod elgamal; @@ -7,3 +10,9 @@ 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/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index 9a51d10434d..7cbaa65e956 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -38,9 +38,6 @@ mod decoding; -use rand_chacha::ChaCha20Rng; -use rand_core::SeedableRng; - use crate::{ crypto::{ ed25519::{sign, PrivateKey, PublicKey, Signature}, @@ -49,8 +46,8 @@ use crate::{ vote_protocol::{ committee::ElectionPublicKey, voter::{ - encrypt_vote, - proof::{generate_voter_proof, VoterProof, VoterProofCommitment}, + encrypt_vote_with_default_rng, + proof::{generate_voter_proof_with_default_rng, VoterProof, VoterProofCommitment}, EncryptedVote, Vote, }, }, @@ -157,19 +154,18 @@ impl VotePayload { ) -> anyhow::Result { let vote = Vote::new(choice.into(), proposal_voting_options.into())?; - let mut rng = ChaCha20Rng::from_entropy(); - let (encrypted_vote, randomness) = encrypt_vote(&vote, election_public_key, &mut rng); + 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( + let voter_proof = generate_voter_proof_with_default_rng( &vote, encrypted_vote.clone(), randomness, election_public_key, &commitment, - &mut rng, )?; Ok(Self::Private(encrypted_vote, voter_proof)) diff --git a/rust/catalyst-voting/src/vote_protocol/committee.rs b/rust/catalyst-voting/src/vote_protocol/committee.rs index 8fd0e0d0b2d..551dfe594f8 100644 --- a/rust/catalyst-voting/src/vote_protocol/committee.rs +++ b/rust/catalyst-voting/src/vote_protocol/committee.rs @@ -1,10 +1,9 @@ //! Module containing all primitives related to the committee. -use std::ops::Mul; - use rand_core::CryptoRngCore; use crate::crypto::{ + default_rng, elgamal::generate_public_key, group::{GroupElement, Scalar}, }; @@ -15,10 +14,17 @@ 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 { @@ -30,13 +36,6 @@ impl ElectionSecretKey { #[derive(Debug, Clone, PartialEq, Eq)] pub struct ElectionPublicKey(pub(crate) GroupElement); -impl ElectionPublicKey { - /// Randomly generate the `ElectionPublicKey`. - pub fn random(rng: &mut R) -> Self { - Self(GroupElement::GENERATOR.mul(&Scalar::random(rng))) - } -} - #[cfg(test)] mod tests { use proptest::prelude::{any, Arbitrary, BoxedStrategy, Strategy}; diff --git a/rust/catalyst-voting/src/vote_protocol/mod.rs b/rust/catalyst-voting/src/vote_protocol/mod.rs index 0d83a0e3bcb..7c2ab504a32 100644 --- a/rust/catalyst-voting/src/vote_protocol/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/mod.rs @@ -5,12 +5,14 @@ //! committee::ElectionSecretKey, //! tally::{ //! decrypt_tally, -//! proof::{generate_tally_proof, verify_tally_proof}, +//! proof::{generate_tally_proof_with_default_rng, verify_tally_proof}, //! tally, DecryptionTallySetup, //! }, //! voter::{ -//! encrypt_vote, -//! proof::{generate_voter_proof, verify_voter_proof, VoterProofCommitment}, +//! encrypt_vote_with_default_rng, +//! proof::{ +//! generate_voter_proof_with_default_rng, verify_voter_proof, VoterProofCommitment, +//! }, //! Vote, //! }, //! }; @@ -21,11 +23,10 @@ //! } //! //! // Initial setup -//! let mut rng = rand_core::OsRng; //! let voting_options = 3; -//! let election_secret_key = ElectionSecretKey::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 voter_1 = Voter { //! voting_power: 10, @@ -48,21 +49,20 @@ //! 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); +//! encrypt_vote_with_default_rng(&vote_3, &election_public_key); //! //! // Verify encrypted votes //! { -//! let voter_proof_1 = generate_voter_proof( +//! 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, -//! &mut rng, //! ) //! .unwrap(); //! assert!(verify_voter_proof( @@ -72,13 +72,12 @@ //! &voter_proof_1 //! )); //! -//! let voter_proof_2 = generate_voter_proof( +//! 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, -//! &mut rng, //! ) //! .unwrap(); //! assert!(verify_voter_proof( @@ -88,13 +87,12 @@ //! &voter_proof_2 //! )); //! -//! let voter_proof_3 = generate_voter_proof( +//! 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, -//! &mut rng, //! ) //! .unwrap(); //! assert!(verify_voter_proof( @@ -129,7 +127,7 @@ //! { //! 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 diff --git a/rust/catalyst-voting/src/vote_protocol/tally/proof.rs b/rust/catalyst-voting/src/vote_protocol/tally/proof.rs index 3f69866b3b4..e043e5d1588 100644 --- a/rust/catalyst-voting/src/vote_protocol/tally/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/tally/proof.rs @@ -8,6 +8,7 @@ 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}, }, @@ -41,6 +42,15 @@ pub fn generate_tally_proof( 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] diff --git a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index a8d141a09f7..361fbf22e24 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -8,6 +8,7 @@ use rand_core::CryptoRngCore; use super::committee::ElectionPublicKey; use crate::crypto::{ + default_rng, elgamal::{encrypt, Ciphertext}, group::Scalar, }; @@ -76,11 +77,12 @@ 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: &ElectionPublicKey, rng: &mut R, ) -> (EncryptedVote, EncryptionRandomness) { @@ -96,6 +98,15 @@ pub fn encrypt_vote( (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()) +} + #[cfg(test)] mod tests { use proptest::{ diff --git a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs index 0c039532c8b..7e669ab25a2 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs @@ -9,6 +9,7 @@ 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}, @@ -32,6 +33,11 @@ impl VoterProofCommitment { 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 { @@ -70,6 +76,27 @@ pub fn generate_voter_proof( 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] diff --git a/rust/catalyst-voting/tests/voting_test.rs b/rust/catalyst-voting/tests/voting_test.rs index 5b5e2efda1c..085a6f76823 100644 --- a/rust/catalyst-voting/tests/voting_test.rs +++ b/rust/catalyst-voting/tests/voting_test.rs @@ -4,12 +4,12 @@ use catalyst_voting::vote_protocol::{ committee::ElectionSecretKey, tally::{ decrypt_tally, - proof::{generate_tally_proof, verify_tally_proof}, + proof::{generate_tally_proof_with_default_rng, verify_tally_proof}, tally, DecryptionTallySetup, }, voter::{ - encrypt_vote, - proof::{generate_voter_proof, verify_voter_proof, VoterProofCommitment}, + encrypt_vote_with_default_rng, + proof::{generate_voter_proof_with_default_rng, verify_voter_proof, VoterProofCommitment}, Vote, }, }; @@ -28,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 = ElectionSecretKey::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() @@ -41,7 +39,7 @@ 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(); // Verify encrypted votes @@ -51,13 +49,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() }) @@ -98,7 +95,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 From 6529b7986142aa76f836814f6b45a6fcac98975d Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 15 Oct 2024 12:13:49 +0300 Subject: [PATCH 37/44] wip --- rust/catalyst-voting/src/txs/v1/mod.rs | 67 ++++++++++++++++++++------ 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index 7cbaa65e956..8b2f485dd85 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -2,19 +2,19 @@ //! //! ```rust //! use catalyst_voting::{ -//! crypto::ed25519::PrivateKey, txs::v1::Tx, vote_protocol::committee::ElectionSecretKey, +//! crypto::{default_rng, ed25519::PrivateKey}, +//! txs::v1::Tx, +//! vote_protocol::committee::ElectionSecretKey, //! }; //! -//! let mut rng = rand_core::OsRng; -//! //! let vote_plan_id = [0u8; 32]; //! let proposal_index = 0u8; //! //! let voting_options = 3; //! let choice = 1; //! -//! let users_private_key = PrivateKey::random(&mut rng); -//! let election_public_key = ElectionSecretKey::random(&mut rng).public_key(); +//! 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, @@ -25,7 +25,7 @@ //! ) //! .unwrap(); //! -//! let private_tx = Tx::new_private( +//! let private_tx = Tx::new_private_with_default_rng( //! vote_plan_id, //! proposal_index, //! voting_options, @@ -38,8 +38,11 @@ mod decoding; +use rand_core::CryptoRngCore; + use crate::{ crypto::{ + default_rng, ed25519::{sign, PrivateKey, PublicKey, Signature}, hash::{digest::Digest, Blake2b256Hasher, Blake2b512Hasher}, }, @@ -47,7 +50,7 @@ use crate::{ committee::ElectionPublicKey, voter::{ encrypt_vote_with_default_rng, - proof::{generate_voter_proof_with_default_rng, VoterProof, VoterProofCommitment}, + proof::{generate_voter_proof, VoterProof, VoterProofCommitment}, EncryptedVote, Vote, }, }, @@ -80,7 +83,7 @@ pub enum VotePayload { } impl Tx { - /// Generate a new `Tx` with public vote + /// Generate a new `Tx` with public vote. /// /// # Errors /// - Invalid voting choice @@ -99,16 +102,47 @@ impl Tx { }) } - /// Generate a new `Tx` with public vote + /// 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( + 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 { - let vote = - VotePayload::new_private(&vote_plan_id, choice, voting_options, election_public_key)?; + let vote = VotePayload::new_private( + &vote_plan_id, + choice, + voting_options, + election_public_key, + &mut default_rng(), + )?; let signature = Self::sign(&vote_plan_id, proposal_index, &vote, users_private_key); Ok(Self { @@ -148,9 +182,9 @@ impl VotePayload { Ok(Self::Public(choice)) } - fn new_private( + fn new_private( vote_plan_id: &[u8; 32], choice: u8, proposal_voting_options: u8, - election_public_key: &ElectionPublicKey, + election_public_key: &ElectionPublicKey, rng: &mut R, ) -> anyhow::Result { let vote = Vote::new(choice.into(), proposal_voting_options.into())?; @@ -160,12 +194,13 @@ impl VotePayload { 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_with_default_rng( + let voter_proof = generate_voter_proof( &vote, encrypted_vote.clone(), randomness, election_public_key, &commitment, + rng, )?; Ok(Self::Private(encrypted_vote, voter_proof)) @@ -196,7 +231,7 @@ mod tests { ) .unwrap(); - let _tx = Tx::new_private( + let _tx = Tx::new_private_with_default_rng( vote_plan_id, proposal_index, voting_options, From d0e870ef0ad1c8d2151b63fdfaada54d69e2245e Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 15 Oct 2024 13:14:24 +0300 Subject: [PATCH 38/44] update v1::Tx decoding --- rust/catalyst-voting/src/txs/v1/decoding.rs | 52 +++++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/rust/catalyst-voting/src/txs/v1/decoding.rs b/rust/catalyst-voting/src/txs/v1/decoding.rs index b517876cfdf..b2ea30338dc 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -24,6 +24,8 @@ 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 { /// Write the bytes to sign for the `Tx` to provided `buf`. @@ -81,6 +83,10 @@ impl Tx { &mut tx_body, ); + // Input tag + tx_body.push(WITNESS_TAG); + // Zero nonce + tx_body.extend_from_slice(&[0u8; 4]); tx_body.extend_from_slice(&self.signature.to_bytes()); // Add the size of decoded bytes to the beginning. @@ -101,36 +107,40 @@ 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)?; + 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}."))?; @@ -144,33 +154,45 @@ 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); From fd87a2efc336170033d95937c30253479ab90994 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 15 Oct 2024 15:59:26 +0300 Subject: [PATCH 39/44] add signature and proof verification --- rust/catalyst-voting/src/txs/v1/decoding.rs | 22 +++--- rust/catalyst-voting/src/txs/v1/mod.rs | 88 ++++++++++++++++++--- 2 files changed, 88 insertions(+), 22 deletions(-) diff --git a/rust/catalyst-voting/src/txs/v1/decoding.rs b/rust/catalyst-voting/src/txs/v1/decoding.rs index b2ea30338dc..23811d92093 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -28,9 +28,9 @@ const PUBLIC_VOTE_TAG: u8 = 1; const WITNESS_TAG: u8 = 2; impl Tx { - /// Write the bytes to sign for the `Tx` to provided `buf`. + /// Write the bytes of the `Tx` body to provided `buf`. #[allow(clippy::cast_possible_truncation)] - pub(super) fn bytes_to_sign( + pub(super) fn tx_body_decode( vote_plan_id: &[u8; 32], proposal_index: u8, vote: &VotePayload, public_key: &PublicKey, buf: &mut Vec, ) { @@ -73,25 +73,25 @@ impl Tx { #[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]; + let mut buf = vec![PADDING_TAG, FRAGMENT_TAG]; - Self::bytes_to_sign( + Self::tx_body_decode( &self.vote_plan_id, self.proposal_index, &self.vote, &self.public_key, - &mut tx_body, + &mut buf, ); - // Input tag - tx_body.push(WITNESS_TAG); + // Witness tag + buf.push(WITNESS_TAG); // Zero nonce - tx_body.extend_from_slice(&[0u8; 4]); - tx_body.extend_from_slice(&self.signature.to_bytes()); + 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 } diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index 8b2f485dd85..a28af738b38 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -38,19 +38,20 @@ mod decoding; +use anyhow::ensure; use rand_core::CryptoRngCore; use crate::{ crypto::{ default_rng, - ed25519::{sign, PrivateKey, PublicKey, Signature}, + ed25519::{sign, verify_signature, PrivateKey, PublicKey, Signature}, hash::{digest::Digest, Blake2b256Hasher, Blake2b512Hasher}, }, vote_protocol::{ committee::ElectionPublicKey, voter::{ encrypt_vote_with_default_rng, - proof::{generate_voter_proof, VoterProof, VoterProofCommitment}, + proof::{generate_voter_proof, verify_voter_proof, VoterProof, VoterProofCommitment}, EncryptedVote, Vote, }, }, @@ -154,22 +155,64 @@ impl Tx { }) } + /// Verify transaction signature and underlying proof for public vote + /// + /// # Errors + /// - Invalid signature + /// - Invalid proof + pub fn verify(&self, election_public_key: &ElectionPublicKey) -> 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." + ); + + 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 mut bytes = Vec::new(); - Self::bytes_to_sign( + let bytes = Self::bytes_to_sign( vote_plan_id, proposal_index, vote, &private_key.public_key(), - &mut bytes, ); - let msg = Blake2b256Hasher::new() + 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(); - sign(private_key, msg.as_slice()) + .finalize() + .to_vec() } } @@ -205,6 +248,27 @@ impl VotePayload { Ok(Self::Private(encrypted_vote, voter_proof)) } + + // #[allow(clippy::cast_possible_truncation, dead_code)] + // fn choice(&self, secret_key: &ElectionSecretKey) -> anyhow::Result { + // match self { + // Self::Public(choice) => Ok(*choice), + // Self::Private(vote, _) => { + // // Making a tally and decryption tally procedure on one vote to retrieve + // the // original voting choice. + // // Assuming that the voting power argument must be equals to 1. + // let setup = DecryptionTallySetup::new(1)?; + // for voting_option in 0..vote.voting_options() { + // let tally = tally(voting_option, &[vote.clone()], &[1])?; + // let choice_for_voting_option = decrypt_tally(&tally, secret_key, + // &setup)?; if choice_for_voting_option == 1 { + // return Ok(voting_option as u8); + // } + // } + // bail!("Invalid encrypted vote, not a unit vector"); + // }, + // } + // } } #[cfg(test)] @@ -216,13 +280,13 @@ mod tests { #[proptest] fn tx_test( - vote_plan_id: [u8; 32], proposal_index: u8, #[strategy(1u8..)] voting_options: u8, + 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( + let tx = Tx::new_public( vote_plan_id, proposal_index, voting_options, @@ -230,8 +294,9 @@ mod tests { &users_private_key, ) .unwrap(); + tx.verify(&election_public_key).unwrap(); - let _tx = Tx::new_private_with_default_rng( + let tx = Tx::new_private_with_default_rng( vote_plan_id, proposal_index, voting_options, @@ -240,5 +305,6 @@ mod tests { &users_private_key, ) .unwrap(); + tx.verify(&election_public_key).unwrap(); } } From e59adee9a9f65fb4f4bd436a2d626cc2731fd67a Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 15 Oct 2024 20:55:50 +0300 Subject: [PATCH 40/44] update verification --- rust/catalyst-voting/src/txs/v1/mod.rs | 53 +++++++++++++++++--------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index a28af738b38..143afd7fc79 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -137,30 +137,34 @@ impl Tx { vote_plan_id: [u8; 32], proposal_index: u8, voting_options: u8, choice: u8, election_public_key: &ElectionPublicKey, users_private_key: &PrivateKey, ) -> anyhow::Result { - let vote = VotePayload::new_private( - &vote_plan_id, - choice, + Self::new_private( + vote_plan_id, + proposal_index, voting_options, + choice, election_public_key, + users_private_key, &mut default_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, - }) + /// Returns `true` if the vote is public + #[must_use] + pub fn is_public(&self) -> bool { + matches!(self.vote, VotePayload::Public(_)) } - /// Verify transaction signature and underlying proof for public vote + /// Returns `true` if the vote is private + #[must_use] + pub fn is_private(&self) -> bool { + matches!(self.vote, VotePayload::Private(_, _)) + } + + /// Verify transaction signature /// /// # Errors /// - Invalid signature - /// - Invalid proof - pub fn verify(&self, election_public_key: &ElectionPublicKey) -> anyhow::Result<()> { + pub fn verify_signature(&self) -> anyhow::Result<()> { let bytes = Self::bytes_to_sign( &self.vote_plan_id, self.proposal_index, @@ -171,7 +175,15 @@ impl Tx { 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); @@ -185,7 +197,6 @@ impl Tx { "Invalid proof." ); } - Ok(()) } @@ -294,7 +305,10 @@ mod tests { &users_private_key, ) .unwrap(); - tx.verify(&election_public_key).unwrap(); + assert!(tx.is_public()); + assert!(!tx.is_private()); + tx.verify_signature().unwrap(); + tx.verify_proof(&election_public_key).unwrap(); let tx = Tx::new_private_with_default_rng( vote_plan_id, @@ -305,6 +319,9 @@ mod tests { &users_private_key, ) .unwrap(); - tx.verify(&election_public_key).unwrap(); + assert!(!tx.is_public()); + assert!(tx.is_private()); + tx.verify_signature().unwrap(); + tx.verify_proof(&election_public_key).unwrap(); } } From 2b8588d8925b0abd162e5759ac930a99143de68a Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 15 Oct 2024 21:15:46 +0300 Subject: [PATCH 41/44] update decoding test --- rust/catalyst-voting/src/txs/v1/decoding.rs | 62 ++++++++++++++++++++- rust/catalyst-voting/src/txs/v1/mod.rs | 3 + 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/rust/catalyst-voting/src/txs/v1/decoding.rs b/rust/catalyst-voting/src/txs/v1/decoding.rs index 23811d92093..b6fe7ef5943 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -265,14 +265,70 @@ mod tests { } #[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 143afd7fc79..c68048b1064 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -24,6 +24,7 @@ //! &users_private_key, //! ) //! .unwrap(); +//! public_tx.verify_signature().unwrap(); //! //! let private_tx = Tx::new_private_with_default_rng( //! vote_plan_id, @@ -34,6 +35,8 @@ //! &users_private_key, //! ) //! .unwrap(); +//! private_tx.verify_signature().unwrap(); +//! private_tx.verify_proof(&election_public_key).unwrap(); //! ``` mod decoding; From 3026d7fa600aff1ecfbf83bd6289afdb472be8e6 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 16 Oct 2024 09:55:53 +0300 Subject: [PATCH 42/44] add decrypt_vote function --- .../src/vote_protocol/voter/mod.rs | 34 +++++++++++++++++-- rust/catalyst-voting/tests/voting_test.rs | 11 +++++- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index 361fbf22e24..8043e7f9551 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -3,13 +3,14 @@ mod decoding; pub mod proof; -use anyhow::ensure; +use anyhow::{anyhow, bail, ensure}; use rand_core::CryptoRngCore; -use super::committee::ElectionPublicKey; +use super::committee::{ElectionPublicKey, ElectionSecretKey}; use crate::crypto::{ + babystep_giantstep::BabyStepGiantStep, default_rng, - elgamal::{encrypt, Ciphertext}, + elgamal::{decrypt, encrypt, Ciphertext}, group::Scalar, }; @@ -107,6 +108,33 @@ pub fn encrypt_vote_with_default_rng( 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 +/// - Ivalid provided encrypted vote, not a 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!("Ivalid provided encrypted vote, not a unit vector."))?; + if choice_per_option == 1 { + return Ok(Vote { + choice: i, + voting_options: vote.0.len(), + }); + } + } + bail!("Ivalid provided encrypted vote, not a unit vector.") +} + #[cfg(test)] mod tests { use proptest::{ diff --git a/rust/catalyst-voting/tests/voting_test.rs b/rust/catalyst-voting/tests/voting_test.rs index 085a6f76823..e4b4242dd5b 100644 --- a/rust/catalyst-voting/tests/voting_test.rs +++ b/rust/catalyst-voting/tests/voting_test.rs @@ -8,7 +8,7 @@ use catalyst_voting::vote_protocol::{ tally, DecryptionTallySetup, }, voter::{ - encrypt_vote_with_default_rng, + decrypt_vote, encrypt_vote_with_default_rng, proof::{generate_voter_proof_with_default_rng, verify_voter_proof, VoterProofCommitment}, Vote, }, @@ -42,6 +42,15 @@ fn voting_test(voters: [Voter; 100]) { .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 From ec8eb9d1478ad178825ad56e07e2f98f66f4c041 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 16 Oct 2024 10:06:50 +0300 Subject: [PATCH 43/44] add private_choice, public_choice methods --- rust/catalyst-voting/src/txs/v1/mod.rs | 56 +++++++++++-------- .../src/vote_protocol/voter/mod.rs | 15 ++++- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index c68048b1064..4d608fb05ce 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -51,9 +51,9 @@ use crate::{ hash::{digest::Digest, Blake2b256Hasher, Blake2b512Hasher}, }, vote_protocol::{ - committee::ElectionPublicKey, + committee::{ElectionPublicKey, ElectionSecretKey}, voter::{ - encrypt_vote_with_default_rng, + decrypt_vote, encrypt_vote_with_default_rng, proof::{generate_voter_proof, verify_voter_proof, VoterProof, VoterProofCommitment}, EncryptedVote, Vote, }, @@ -163,6 +163,33 @@ impl Tx { 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")) + } + } + + /// 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 @@ -262,27 +289,6 @@ impl VotePayload { Ok(Self::Private(encrypted_vote, voter_proof)) } - - // #[allow(clippy::cast_possible_truncation, dead_code)] - // fn choice(&self, secret_key: &ElectionSecretKey) -> anyhow::Result { - // match self { - // Self::Public(choice) => Ok(*choice), - // Self::Private(vote, _) => { - // // Making a tally and decryption tally procedure on one vote to retrieve - // the // original voting choice. - // // Assuming that the voting power argument must be equals to 1. - // let setup = DecryptionTallySetup::new(1)?; - // for voting_option in 0..vote.voting_options() { - // let tally = tally(voting_option, &[vote.clone()], &[1])?; - // let choice_for_voting_option = decrypt_tally(&tally, secret_key, - // &setup)?; if choice_for_voting_option == 1 { - // return Ok(voting_option as u8); - // } - // } - // bail!("Invalid encrypted vote, not a unit vector"); - // }, - // } - // } } #[cfg(test)] @@ -312,6 +318,8 @@ mod tests { 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, @@ -326,5 +334,7 @@ mod tests { 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/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index 8043e7f9551..c3278a231bd 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -64,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) @@ -114,7 +120,7 @@ pub fn encrypt_vote_with_default_rng( /// If not valid encrypted vote is provided, unexpected results may occur. /// /// # Errors -/// - Ivalid provided encrypted vote, not a unit vector. +/// - Ivalid 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`. @@ -124,7 +130,7 @@ pub fn decrypt_vote(vote: &EncryptedVote, secret_key: &ElectionSecretKey) -> any 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!("Ivalid provided encrypted vote, not a unit vector."))?; + .map_err(|_| anyhow!("Ivalid encrypted vote, not a valid unit vector."))?; if choice_per_option == 1 { return Ok(Vote { choice: i, @@ -132,7 +138,7 @@ pub fn decrypt_vote(vote: &EncryptedVote, secret_key: &ElectionSecretKey) -> any }); } } - bail!("Ivalid provided encrypted vote, not a unit vector.") + bail!("Ivalid encrypted vote, not a valid unit vector.") } #[cfg(test)] @@ -160,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(), @@ -167,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(), @@ -174,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(), From 474aa4ea4428ed9fdf316bd869c1961e34d69449 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 16 Oct 2024 10:47:19 +0300 Subject: [PATCH 44/44] fix spelling --- .config/dictionaries/project.dic | 1 + rust/catalyst-voting/src/crypto/mod.rs | 2 ++ rust/catalyst-voting/src/txs/v1/mod.rs | 2 +- rust/catalyst-voting/src/vote_protocol/voter/mod.rs | 6 +++--- 4 files changed, 7 insertions(+), 4 deletions(-) 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/src/crypto/mod.rs b/rust/catalyst-voting/src/crypto/mod.rs index 7ad5b43eb74..0be3852f7bb 100644 --- a/rust/catalyst-voting/src/crypto/mod.rs +++ b/rust/catalyst-voting/src/crypto/mod.rs @@ -1,5 +1,7 @@ //! Crypto primitives which are used by voting protocol. +// cspell: words Seedable + use rand_chacha::ChaCha8Rng; use rand_core::{CryptoRngCore, SeedableRng}; diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs index 4d608fb05ce..fbc19d6e9e3 100644 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ b/rust/catalyst-voting/src/txs/v1/mod.rs @@ -77,7 +77,7 @@ pub struct Tx { } /// Vote payload struct. -/// Contains all necesarry information for the valid vote. +/// Contains all necessary information for the valid vote. #[derive(Debug, Clone, PartialEq, Eq)] pub enum VotePayload { /// Public voting choice diff --git a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index c3278a231bd..bb267a6253a 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -120,7 +120,7 @@ pub fn encrypt_vote_with_default_rng( /// If not valid encrypted vote is provided, unexpected results may occur. /// /// # Errors -/// - Ivalid encrypted vote, not a valid unit vector. +/// - 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`. @@ -130,7 +130,7 @@ pub fn decrypt_vote(vote: &EncryptedVote, secret_key: &ElectionSecretKey) -> any 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!("Ivalid encrypted vote, not a valid unit vector."))?; + .map_err(|_| anyhow!("Invalid encrypted vote, not a valid unit vector."))?; if choice_per_option == 1 { return Ok(Vote { choice: i, @@ -138,7 +138,7 @@ pub fn decrypt_vote(vote: &EncryptedVote, secret_key: &ElectionSecretKey) -> any }); } } - bail!("Ivalid encrypted vote, not a valid unit vector.") + bail!("Invalid encrypted vote, not a valid unit vector.") } #[cfg(test)]