From 8e2d1d46bd4f55a2ebad270e45693b9151f5ac93 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 9 Oct 2024 09:58:05 +0300 Subject: [PATCH 01/19] 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 16debee045..14871d42f5 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 0000000000..16fb14d7bc --- /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 81cd10990e..ee63f76ae3 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 75752d524b..1cd5a05878 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/19] 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 14871d42f5..ac06b9e8dd 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 0000000000..ed61e56141 --- /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 0000000000..fec47ef756 --- /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 0000000000..7ea9515717 --- /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/19] 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 737c02a7d9..3fb1d6f216 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 71615aadbe..c65924efee 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/19] 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 3fb1d6f216..520f384709 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 7ea9515717..5b3d090cf1 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 e22d777c3a..32271af5ec 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/19] 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 520f384709..3852e25d4e 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 38951ead3b..0132e6bb10 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 32271af5ec..de3aa1a235 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/19] 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 de3aa1a235..b587590571 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/19] 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 42ef8bbdd0..32efae4341 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 8dd849864b..aded9035f0 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 0132e6bb10..460b87205f 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 5b3d090cf1..b3e86b1d01 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 b587590571..1f377bc7ff 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 8df75db4b0..f9550ba6aa 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/19] 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 b3e86b1d01..a22bd00db3 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 f9550ba6aa..d668e93037 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/19] 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 866437d4e2..df6e2aa051 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 a5fceb2feb..89b8436fae 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 3852e25d4e..f4e05ec67e 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 c65924efee..90bb8853cd 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 aded9035f0..c5a45b0cc6 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 460b87205f..5610eb80c3 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 ed61e56141..c430e0d294 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 ee63f76ae3..7e29cc2cae 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 1f377bc7ff..0f9a197328 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 d668e93037..6855420ffa 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/19] 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 0000000000..b9f094d85b --- /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 f4e05ec67e..aefed68f66 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 0000000000..4076ee9b09 --- /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 90bb8853cd..b636f8a864 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 0000000000..3554e03e3a --- /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 c5a45b0cc6..968988ef0c 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 5610eb80c3..4771a04a4a 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 0000000000..8d8519dea9 --- /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 0f9a197328..1537dbdcb2 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 6855420ffa..f9a70b50e8 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/19] 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 b9f094d85b..e2d82309ce 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 3554e03e3a..916484a0c6 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 c430e0d294..71e3abe603 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 fec47ef756..0000000000 --- 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 a22bd00db3..0000000000 --- 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 0000000000..2006a9b73c --- /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 0000000000..eadffe80e5 --- /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 8d8519dea9..b67e99dfdf 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 1537dbdcb2..788bcca294 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/19] 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 916484a0c6..2e80507b8f 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 2006a9b73c..e2755ecae4 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 eadffe80e5..a1c00d3854 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 b67e99dfdf..72e9d8179c 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/19] 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 2e80507b8f..e693f18eb7 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 e2755ecae4..732df80c1e 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/19] 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 e693f18eb7..0bcb222ffd 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 732df80c1e..d6d66260b7 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 72e9d8179c..37bbf36fa5 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/19] 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 0bcb222ffd..a050fb2a41 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 d6d66260b7..ac6d6ec3bd 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/19] 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 a050fb2a41..82b1229fb3 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 ac06b9e8dd..b494392750 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 ac6d6ec3bd..60527d47e9 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 0000000000..393c00fb71 --- /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 37bbf36fa5..f4f08cc866 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/19] 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 60527d47e9..467927632e 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 a1c00d3854..a78a1db10c 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/19] 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 968988ef0c..1f2c21bb42 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 8db8ae76ace321eb106129c2e66e69535c46ef6f Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Fri, 11 Oct 2024 16:39:21 +0300 Subject: [PATCH 19/19] add consts --- rust/catalyst-voting/src/txs/v1/decoding.rs | 57 ++++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/rust/catalyst-voting/src/txs/v1/decoding.rs b/rust/catalyst-voting/src/txs/v1/decoding.rs index 467927632e..2562830225 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/catalyst-voting/src/txs/v1/decoding.rs @@ -7,13 +7,28 @@ 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}; +/// Jörmungandr tx fragment tag. +const FRAGMENT_TAG: u8 = 11; +/// Jörmungandr tx input tag. +const INPUT_TAG: u8 = 0xFF; +/// Jörmungandr tx number of inputs. +const NUMBER_OF_INPUTS: u8 = 1; +/// Jörmungandr tx number of outputs. +const NUMBER_OF_OUTPUTS: u8 = 0; +/// Jörmungandr tx padding tag. +const PADDING_TAG: u8 = 0; +/// Jörmungandr tx private vote tag. +const PRIVATE_VOTE_TAG: u8 = 2; +/// Jörmungandr tx public vote tag. +const PUBLIC_VOTE_TAG: u8 = 1; + 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]; + let mut tx_body = vec![PADDING_TAG, FRAGMENT_TAG]; tx_body.extend_from_slice(&self.vote_plan_id); tx_body.push(self.proposal_index); @@ -21,12 +36,12 @@ impl Tx { match &self.vote { Vote::Public(vote) => { // Public vote tag - tx_body.push(1); + tx_body.push(PUBLIC_VOTE_TAG); tx_body.push(*vote); }, Vote::Private(vote, proof) => { // Private vote tag - tx_body.push(2); + tx_body.push(PRIVATE_VOTE_TAG); tx_body.push(vote.size() as u8); tx_body.extend_from_slice(&vote.to_bytes()); @@ -38,11 +53,11 @@ impl Tx { // Zeros block date tx_body.extend_from_slice(&[0u8; 8]); // Number of inputs - tx_body.push(1); + tx_body.push(NUMBER_OF_INPUTS); // Number of outputs - tx_body.push(0); + tx_body.push(NUMBER_OF_OUTPUTS); // Input tag - tx_body.push(0xFF); + tx_body.push(INPUT_TAG); // Zero value tx_body.extend_from_slice(&[0u8; 8]); @@ -70,14 +85,14 @@ impl Tx { let padding_tag = read_be_u8(reader)?; ensure!( - padding_tag == 0, - "Invalid padding tag field value, must be equals to `0`, provided: {padding_tag}.", + 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)?; ensure!( - fragment_tag == 11, - "Invalid fragment tag field value, must be equals to `11`, provided: {fragment_tag}.", + 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)?; @@ -86,11 +101,11 @@ impl Tx { let vote_tag = read_be_u8(reader)?; let vote = match vote_tag { - 1 => { + PUBLIC_VOTE_TAG => { let vote = read_be_u8(reader)?; Vote::Public(vote) }, - 2 => { + PRIVATE_VOTE_TAG => { let size = read_be_u8(reader)?; let vote = EncryptedVote::from_bytes(reader, size.into()) .map_err(|e| anyhow!("Invalid encrypted vote, error: {e}."))?; @@ -101,7 +116,11 @@ impl Tx { Vote::Private(vote, proof) }, - tag => bail!("Invalid vote tag value, must be equals to `0` or `1`, provided: {tag}"), + tag => { + bail!( + "Invalid vote tag value, must be equals to {PUBLIC_VOTE_TAG} or {PRIVATE_VOTE_TAG}, provided: {tag}" + ) + }, }; // skip block date (epoch and slot) @@ -109,20 +128,20 @@ impl Tx { let inputs_amount = read_be_u8(reader)?; ensure!( - inputs_amount == 1, - "Invalid number of inputs, expected: `1`, provided: {inputs_amount}", + inputs_amount == NUMBER_OF_INPUTS, + "Invalid number of inputs, expected: {NUMBER_OF_INPUTS}, provided: {inputs_amount}", ); let outputs_amount = read_be_u8(reader)?; ensure!( - outputs_amount == 0, - "Invalid number of outputs, expected: `0`, provided: {outputs_amount}", + outputs_amount == NUMBER_OF_OUTPUTS, + "Invalid number of outputs, expected: {NUMBER_OF_OUTPUTS}, provided: {outputs_amount}", ); let input_tag = read_be_u8(reader)?; ensure!( - input_tag == 0xFF, - "Invalid input tag, expected: `255`, provided: {input_tag}", + input_tag == INPUT_TAG, + "Invalid input tag, expected: {INPUT_TAG}, provided: {input_tag}", ); // skip value