From 8f8862c5799cfa026ed945d789dc4d116daf82ab Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 31 Oct 2024 12:40:59 +0200 Subject: [PATCH 01/10] add new jormungandr-vote-tx crate --- rust/Cargo.toml | 2 +- rust/jormungandr-vote-tx/Cargo.toml | 19 +++++++++++++++++++ rust/jormungandr-vote-tx/src/lib.rs | 0 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 rust/jormungandr-vote-tx/Cargo.toml create mode 100644 rust/jormungandr-vote-tx/src/lib.rs diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 930e2fc19cd..94ed9ac812e 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -7,7 +7,7 @@ members = [ "cbork", "cbork-abnf-parser", "cbork-cddl-parser", - "catalyst-voting", + "catalyst-voting", "jormungandr-vote-tx", ] [workspace.package] diff --git a/rust/jormungandr-vote-tx/Cargo.toml b/rust/jormungandr-vote-tx/Cargo.toml new file mode 100644 index 00000000000..1a9281a3f55 --- /dev/null +++ b/rust/jormungandr-vote-tx/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "jormungandr-vote-tx" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +catalyst-voting = { path = "../catalyst-voting" } + +proptest = { version = "1.5.0" } +# Potentially it could be replaced with using `proptest::property_test` attribute macro, +# after this PR will be merged https://github.com/proptest-rs/proptest/pull/523 +test-strategy = "0.4.0" + +[lints] +workspace = true diff --git a/rust/jormungandr-vote-tx/src/lib.rs b/rust/jormungandr-vote-tx/src/lib.rs new file mode 100644 index 00000000000..e69de29bb2d From a68560229f198c14793f82c70f83546501c7620a Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 31 Oct 2024 13:06:56 +0200 Subject: [PATCH 02/10] move code from the catalyst-voting crate to jorm-vote-tx --- rust/catalyst-voting/benches/vote_protocol.rs | 2 +- .../catalyst-voting/src/crypto/ed25519/mod.rs | 3 +- .../src/crypto/group/ristretto255/mod.rs | 6 +- rust/catalyst-voting/src/crypto/mod.rs | 12 +- rust/catalyst-voting/src/crypto/rng.rs | 14 + .../src/crypto/zk_unit_vector/mod.rs | 2 +- .../randomness_announcements.rs | 7 +- rust/catalyst-voting/src/lib.rs | 1 - rust/catalyst-voting/src/txs/mod.rs | 3 - rust/catalyst-voting/src/txs/v1/mod.rs | 339 ------------------ rust/catalyst-voting/src/utils.rs | 24 -- .../src/vote_protocol/committee/mod.rs | 4 +- .../src/vote_protocol/tally/proof.rs | 4 +- .../src/vote_protocol/voter/decoding.rs | 3 +- .../src/vote_protocol/voter/mod.rs | 3 +- .../src/vote_protocol/voter/proof.rs | 3 +- rust/jormungandr-vote-tx/Cargo.toml | 9 +- .../src}/decoding.rs | 256 ++++++------- rust/jormungandr-vote-tx/src/lib.rs | 339 ++++++++++++++++++ rust/jormungandr-vote-tx/src/utils.rs | 35 ++ 20 files changed, 540 insertions(+), 529 deletions(-) create mode 100644 rust/catalyst-voting/src/crypto/rng.rs delete mode 100644 rust/catalyst-voting/src/txs/mod.rs delete mode 100644 rust/catalyst-voting/src/txs/v1/mod.rs rename rust/{catalyst-voting/src/txs/v1 => jormungandr-vote-tx/src}/decoding.rs (60%) create mode 100644 rust/jormungandr-vote-tx/src/utils.rs diff --git a/rust/catalyst-voting/benches/vote_protocol.rs b/rust/catalyst-voting/benches/vote_protocol.rs index 55f9227a949..4c2c40f5bcf 100644 --- a/rust/catalyst-voting/benches/vote_protocol.rs +++ b/rust/catalyst-voting/benches/vote_protocol.rs @@ -12,7 +12,7 @@ )] use catalyst_voting::{ - crypto::default_rng, + crypto::rng::default_rng, vote_protocol::{ committee::{ElectionPublicKey, ElectionSecretKey}, tally::{ diff --git a/rust/catalyst-voting/src/crypto/ed25519/mod.rs b/rust/catalyst-voting/src/crypto/ed25519/mod.rs index a9cc4488e54..ca0557cd4db 100644 --- a/rust/catalyst-voting/src/crypto/ed25519/mod.rs +++ b/rust/catalyst-voting/src/crypto/ed25519/mod.rs @@ -5,7 +5,8 @@ mod decoding; use ed25519_dalek::{ ed25519::signature::Signer, Signature as Ed25519Signature, SigningKey, VerifyingKey, }; -use rand_core::CryptoRngCore; + +use crate::crypto::rng::rand_core::CryptoRngCore; /// `Ed25519` private key struct. #[must_use] diff --git a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs index d291da41828..1e66c947d88 100644 --- a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs +++ b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs @@ -15,9 +15,11 @@ use curve25519_dalek::{ traits::Identity, RistrettoPoint, }; -use rand_core::CryptoRngCore; -use crate::crypto::hash::digest::{consts::U64, Digest}; +use crate::crypto::{ + hash::digest::{consts::U64, Digest}, + rng::rand_core::CryptoRngCore, +}; /// Ristretto group scalar. #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/rust/catalyst-voting/src/crypto/mod.rs b/rust/catalyst-voting/src/crypto/mod.rs index 0be3852f7bb..626100a456a 100644 --- a/rust/catalyst-voting/src/crypto/mod.rs +++ b/rust/catalyst-voting/src/crypto/mod.rs @@ -1,20 +1,10 @@ //! Crypto primitives which are used by voting protocol. -// cspell: words Seedable - -use rand_chacha::ChaCha8Rng; -use rand_core::{CryptoRngCore, SeedableRng}; - pub mod babystep_giantstep; pub mod ed25519; pub mod elgamal; pub mod group; pub mod hash; +pub mod rng; pub mod zk_dl_equality; pub mod zk_unit_vector; - -/// Default random number generator `rand_chacha::ChaCha8Rng`. -#[must_use] -pub fn default_rng() -> impl CryptoRngCore { - ChaCha8Rng::from_entropy() -} diff --git a/rust/catalyst-voting/src/crypto/rng.rs b/rust/catalyst-voting/src/crypto/rng.rs new file mode 100644 index 00000000000..de7c2c42803 --- /dev/null +++ b/rust/catalyst-voting/src/crypto/rng.rs @@ -0,0 +1,14 @@ +//! Random number generator objects. + +// cspell: words Seedable + +use rand_chacha::ChaCha8Rng; +pub use rand_core; +use rand_core::{CryptoRngCore, SeedableRng}; + +/// Default random number generator `rand_chacha::ChaCha8Rng`. +#[must_use] +#[allow(clippy::module_name_repetitions)] +pub fn default_rng() -> impl CryptoRngCore { + ChaCha8Rng::from_entropy() +} 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 4d0bc6a2431..b63d6a9d7b7 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs @@ -16,7 +16,6 @@ use std::ops::Mul; use challenges::{calculate_first_challenge_hash, calculate_second_challenge_hash}; use polynomial::{calculate_polynomial_val, generate_polynomial, Polynomial}; -use rand_core::CryptoRngCore; use randomness_announcements::{Announcement, BlindingRandomness, ResponseRandomness}; use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; use utils::get_bit; @@ -24,6 +23,7 @@ use utils::get_bit; use crate::crypto::{ elgamal::{encrypt, Ciphertext}, group::{GroupElement, Scalar}, + rng::rand_core::CryptoRngCore, }; /// Unit vector proof struct 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 4771a04a4a3..580a2c3852e 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,9 +4,10 @@ use std::ops::Mul; -use rand_core::CryptoRngCore; - -use crate::crypto::group::{GroupElement, Scalar}; +use crate::crypto::{ + group::{GroupElement, Scalar}, + rng::rand_core::CryptoRngCore, +}; /// Randomness generated in the proof, used for the hiding property. #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/rust/catalyst-voting/src/lib.rs b/rust/catalyst-voting/src/lib.rs index e0d911e7180..b165d78f1ca 100644 --- a/rust/catalyst-voting/src/lib.rs +++ b/rust/catalyst-voting/src/lib.rs @@ -1,6 +1,5 @@ //! Voting primitives which are used among Catalyst ecosystem. pub mod crypto; -pub mod txs; mod utils; pub mod vote_protocol; diff --git a/rust/catalyst-voting/src/txs/mod.rs b/rust/catalyst-voting/src/txs/mod.rs deleted file mode 100644 index 71e3abe6036..00000000000 --- a/rust/catalyst-voting/src/txs/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! A catalyst transaction objects implementation - -pub mod v1; diff --git a/rust/catalyst-voting/src/txs/v1/mod.rs b/rust/catalyst-voting/src/txs/v1/mod.rs deleted file mode 100644 index c98bfe28353..00000000000 --- a/rust/catalyst-voting/src/txs/v1/mod.rs +++ /dev/null @@ -1,339 +0,0 @@ -//! A Jörmungandr transaction object structured following this [spec](https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/catalyst_voting/transaction/#v1-jormungandr) -//! -//! ```rust -//! use catalyst_voting::{ -//! crypto::{default_rng, ed25519::PrivateKey}, -//! txs::v1::Tx, -//! vote_protocol::committee::ElectionSecretKey, -//! }; -//! -//! let vote_plan_id = [0u8; 32]; -//! let proposal_index = 0u8; -//! -//! let voting_options = 3; -//! let choice = 1; -//! -//! let users_private_key = PrivateKey::random(&mut default_rng()); -//! let election_public_key = ElectionSecretKey::random_with_default_rng().public_key(); -//! -//! let public_tx = Tx::new_public( -//! vote_plan_id, -//! proposal_index, -//! voting_options, -//! choice, -//! &users_private_key, -//! ) -//! .unwrap(); -//! public_tx.verify_signature().unwrap(); -//! -//! let private_tx = Tx::new_private_with_default_rng( -//! vote_plan_id, -//! proposal_index, -//! voting_options, -//! choice, -//! &election_public_key, -//! &users_private_key, -//! ) -//! .unwrap(); -//! private_tx.verify_signature().unwrap(); -//! private_tx.verify_proof(&election_public_key).unwrap(); -//! ``` - -mod decoding; - -use anyhow::ensure; -use rand_core::CryptoRngCore; - -use crate::{ - crypto::{ - default_rng, - ed25519::{sign, verify_signature, PrivateKey, PublicKey, Signature}, - hash::{digest::Digest, Blake2b256Hasher, Blake2b512Hasher}, - }, - vote_protocol::{ - committee::{ElectionPublicKey, ElectionSecretKey}, - voter::{ - decrypt_vote, encrypt_vote, - proof::{generate_voter_proof, verify_voter_proof, VoterProof, VoterProofCommitment}, - EncryptedVote, Vote, - }, - }, -}; - -/// A v1 (Jörmungandr) transaction struct -#[derive(Debug, Clone, PartialEq, Eq)] -#[must_use] -pub struct Tx { - /// Vote plan id - vote_plan_id: [u8; 32], - /// Proposal index - proposal_index: u8, - /// Vote - vote: VotePayload, - /// Public key - public_key: PublicKey, - /// Transaction signature - signature: Signature, -} - -/// Vote payload struct. -/// Contains all necessary information for the valid vote. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum VotePayload { - /// Public voting choice - Public(u8), - /// Private (encrypted) voting choice - Private(EncryptedVote, VoterProof), -} - -impl Tx { - /// Generate a new `Tx` with public vote. - /// - /// # Errors - /// - Invalid voting choice - pub fn new_public( - vote_plan_id: [u8; 32], proposal_index: u8, voting_options: u8, choice: u8, - users_private_key: &PrivateKey, - ) -> anyhow::Result { - let vote = VotePayload::new_public(choice, voting_options)?; - let signature = Self::sign(&vote_plan_id, proposal_index, &vote, users_private_key); - Ok(Self { - vote_plan_id, - proposal_index, - vote, - public_key: users_private_key.public_key(), - signature, - }) - } - - /// Generate a new `Tx` with public vote. - /// - /// # Errors - /// - Invalid voting choice - pub fn new_private( - vote_plan_id: [u8; 32], proposal_index: u8, voting_options: u8, choice: u8, - election_public_key: &ElectionPublicKey, users_private_key: &PrivateKey, rng: &mut R, - ) -> anyhow::Result { - let vote = VotePayload::new_private( - &vote_plan_id, - choice, - voting_options, - election_public_key, - rng, - )?; - let signature = Self::sign(&vote_plan_id, proposal_index, &vote, users_private_key); - - Ok(Self { - vote_plan_id, - proposal_index, - vote, - public_key: users_private_key.public_key(), - signature, - }) - } - - /// Generate a new `Tx` with public vote with `crypto::default_rng`. - /// - /// # Errors - /// - Invalid voting choice - pub fn new_private_with_default_rng( - vote_plan_id: [u8; 32], proposal_index: u8, voting_options: u8, choice: u8, - election_public_key: &ElectionPublicKey, users_private_key: &PrivateKey, - ) -> anyhow::Result { - Self::new_private( - vote_plan_id, - proposal_index, - voting_options, - choice, - election_public_key, - users_private_key, - &mut default_rng(), - ) - } - - /// Returns `true` if the vote is public - #[must_use] - pub fn is_public(&self) -> bool { - matches!(self.vote, VotePayload::Public(_)) - } - - /// Returns `true` if the vote is private - #[must_use] - pub fn is_private(&self) -> bool { - matches!(self.vote, VotePayload::Private(_, _)) - } - - /// Returns public voting choice. - /// - /// # Errors - /// - Not a public vote - pub fn public_choice(&self) -> anyhow::Result { - if let VotePayload::Public(choice) = &self.vote { - Ok(*choice) - } else { - Err(anyhow::anyhow!("Not a public vote")) - } - } - - /// Returns private voting choice. - /// - /// # Errors - /// - Not a private vote - #[allow(clippy::cast_possible_truncation)] - pub fn private_choice(&self, secret_key: &ElectionSecretKey) -> anyhow::Result { - if let VotePayload::Private(vote, _) = &self.vote { - let vote = decrypt_vote(vote, secret_key)?; - let choice = vote.choice() as u8; - Ok(choice) - } else { - Err(anyhow::anyhow!("Not a private vote")) - } - } - - /// Verify transaction signature - /// - /// # Errors - /// - Invalid signature - pub fn verify_signature(&self) -> anyhow::Result<()> { - let bytes = Self::bytes_to_sign( - &self.vote_plan_id, - self.proposal_index, - &self.vote, - &self.public_key, - ); - ensure!( - verify_signature(&self.public_key, &bytes, &self.signature), - "Invalid signature." - ); - Ok(()) - } - - /// Verify transaction proof of the private vote. - /// If vote is public it returns `Ok(())` - /// - /// # Errors - /// - Invalid proof - pub fn verify_proof(&self, election_public_key: &ElectionPublicKey) -> anyhow::Result<()> { - if let VotePayload::Private(encrypted_vote, proof) = &self.vote { - let vote_plan_id_hash = Blake2b512Hasher::new().chain_update(self.vote_plan_id); - let commitment = VoterProofCommitment::from_hash(vote_plan_id_hash); - ensure!( - verify_voter_proof( - encrypted_vote.clone(), - election_public_key, - &commitment, - proof, - ), - "Invalid proof." - ); - } - Ok(()) - } - - /// Generate transaction signature - fn sign( - vote_plan_id: &[u8; 32], proposal_index: u8, vote: &VotePayload, private_key: &PrivateKey, - ) -> Signature { - let bytes = Self::bytes_to_sign( - vote_plan_id, - proposal_index, - vote, - &private_key.public_key(), - ); - sign(private_key, &bytes) - } - - /// Generate bytes to be signed. - /// A Blake2b256 hash of the transaction body - fn bytes_to_sign( - vote_plan_id: &[u8; 32], proposal_index: u8, vote: &VotePayload, public_key: &PublicKey, - ) -> Vec { - let mut bytes = Vec::new(); - Self::tx_body_decode(vote_plan_id, proposal_index, vote, public_key, &mut bytes); - Blake2b256Hasher::new() - .chain_update(bytes.as_slice()) - .finalize() - .to_vec() - } -} - -#[allow(clippy::missing_docs_in_private_items)] -impl VotePayload { - fn new_public(choice: u8, proposal_voting_options: u8) -> anyhow::Result { - // Try to make a `Vote` just for applying underlying validation, which must be the same - // even for public vote - Vote::new(choice.into(), proposal_voting_options.into())?; - Ok(Self::Public(choice)) - } - - fn new_private( - vote_plan_id: &[u8; 32], choice: u8, proposal_voting_options: u8, - election_public_key: &ElectionPublicKey, rng: &mut R, - ) -> anyhow::Result { - let vote = Vote::new(choice.into(), proposal_voting_options.into())?; - - let (encrypted_vote, randomness) = encrypt_vote(&vote, election_public_key, rng); - - let vote_plan_id_hash = Blake2b512Hasher::new().chain_update(vote_plan_id); - let commitment = VoterProofCommitment::from_hash(vote_plan_id_hash); - - let voter_proof = generate_voter_proof( - &vote, - encrypted_vote.clone(), - randomness, - election_public_key, - &commitment, - rng, - )?; - - Ok(Self::Private(encrypted_vote, voter_proof)) - } -} - -#[cfg(test)] -mod tests { - use test_strategy::proptest; - - use super::*; - use crate::{crypto::ed25519::PrivateKey, vote_protocol::committee::ElectionSecretKey}; - - #[proptest] - fn tx_test( - vote_plan_id: [u8; 32], proposal_index: u8, #[strategy(1u8..5)] voting_options: u8, - #[strategy(0..#voting_options)] choice: u8, users_private_key: PrivateKey, - election_secret_key: ElectionSecretKey, - ) { - let election_public_key = election_secret_key.public_key(); - - let tx = Tx::new_public( - vote_plan_id, - proposal_index, - voting_options, - choice, - &users_private_key, - ) - .unwrap(); - assert!(tx.is_public()); - assert!(!tx.is_private()); - tx.verify_signature().unwrap(); - tx.verify_proof(&election_public_key).unwrap(); - assert_eq!(tx.public_choice().unwrap(), choice); - assert!(tx.private_choice(&election_secret_key).is_err()); - - let tx = Tx::new_private_with_default_rng( - vote_plan_id, - proposal_index, - voting_options, - choice, - &election_public_key, - &users_private_key, - ) - .unwrap(); - assert!(!tx.is_public()); - assert!(tx.is_private()); - tx.verify_signature().unwrap(); - tx.verify_proof(&election_public_key).unwrap(); - assert_eq!(tx.private_choice(&election_secret_key).unwrap(), choice); - assert!(tx.public_choice().is_err()); - } -} diff --git a/rust/catalyst-voting/src/utils.rs b/rust/catalyst-voting/src/utils.rs index 393c00fb71f..95198847ade 100644 --- a/rust/catalyst-voting/src/utils.rs +++ b/rust/catalyst-voting/src/utils.rs @@ -2,30 +2,6 @@ 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]> { diff --git a/rust/catalyst-voting/src/vote_protocol/committee/mod.rs b/rust/catalyst-voting/src/vote_protocol/committee/mod.rs index 0f11ea0bfde..dfefeacf1d4 100644 --- a/rust/catalyst-voting/src/vote_protocol/committee/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/committee/mod.rs @@ -2,12 +2,10 @@ mod decoding; -use rand_core::CryptoRngCore; - use crate::crypto::{ - default_rng, elgamal::generate_public_key, group::{GroupElement, Scalar}, + rng::{default_rng, rand_core::CryptoRngCore}, }; /// Election secret key. diff --git a/rust/catalyst-voting/src/vote_protocol/tally/proof.rs b/rust/catalyst-voting/src/vote_protocol/tally/proof.rs index 46d89844b1b..fbc8a16f303 100644 --- a/rust/catalyst-voting/src/vote_protocol/tally/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/tally/proof.rs @@ -3,13 +3,11 @@ use std::ops::Mul; -use rand_core::CryptoRngCore; - use super::EncryptedTally; use crate::{ crypto::{ - default_rng, group::{GroupElement, Scalar}, + rng::{default_rng, rand_core::CryptoRngCore}, zk_dl_equality::{generate_dleq_proof, verify_dleq_proof, DleqProof}, }, vote_protocol::committee::{ElectionPublicKey, ElectionSecretKey}, diff --git a/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs b/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs index f4f08cc866d..8924d52513d 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/decoding.rs @@ -12,7 +12,8 @@ use crate::{ impl EncryptedVote { /// Get an underlying vector length. - pub(crate) fn size(&self) -> usize { + #[must_use] + pub fn size(&self) -> usize { self.0.len() } diff --git a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index 7937faa4977..44e8c641309 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -4,15 +4,14 @@ mod decoding; pub mod proof; use anyhow::{anyhow, bail, ensure}; -use rand_core::CryptoRngCore; use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; use super::committee::{ElectionPublicKey, ElectionSecretKey}; use crate::crypto::{ babystep_giantstep::BabyStepGiantStep, - default_rng, elgamal::{decrypt, encrypt, Ciphertext}, group::Scalar, + rng::{default_rng, rand_core::CryptoRngCore}, }; /// A representation of the voter's voting choice. diff --git a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs index 2f74a4ac01d..cc85468c1b1 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs @@ -4,14 +4,13 @@ use std::ops::Mul; use anyhow::ensure; -use rand_core::CryptoRngCore; use super::{EncryptedVote, EncryptionRandomness, Vote}; use crate::{ crypto::{ - default_rng, group::{GroupElement, Scalar}, hash::digest::{consts::U64, Digest}, + rng::{default_rng, rand_core::CryptoRngCore}, zk_unit_vector::{generate_unit_vector_proof, verify_unit_vector_proof, UnitVectorProof}, }, vote_protocol::committee::ElectionPublicKey, diff --git a/rust/jormungandr-vote-tx/Cargo.toml b/rust/jormungandr-vote-tx/Cargo.toml index 1a9281a3f55..9bccdcabab6 100644 --- a/rust/jormungandr-vote-tx/Cargo.toml +++ b/rust/jormungandr-vote-tx/Cargo.toml @@ -1,19 +1,20 @@ [package] name = "jormungandr-vote-tx" -version = "0.1.0" +version = "0.0.1" edition.workspace = true authors.workspace = true homepage.workspace = true repository.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] catalyst-voting = { path = "../catalyst-voting" } +anyhow = "1.0.89" proptest = { version = "1.5.0" } # Potentially it could be replaced with using `proptest::property_test` attribute macro, # after this PR will be merged https://github.com/proptest-rs/proptest/pull/523 test-strategy = "0.4.0" - -[lints] -workspace = true diff --git a/rust/catalyst-voting/src/txs/v1/decoding.rs b/rust/jormungandr-vote-tx/src/decoding.rs similarity index 60% rename from rust/catalyst-voting/src/txs/v1/decoding.rs rename to rust/jormungandr-vote-tx/src/decoding.rs index 58e56c517a2..228be697780 100644 --- a/rust/catalyst-voting/src/txs/v1/decoding.rs +++ b/rust/jormungandr-vote-tx/src/decoding.rs @@ -3,11 +3,11 @@ use std::io::Read; use anyhow::{anyhow, bail, ensure}; +use catalyst_voting::crypto::ed25519::{PublicKey, Signature}; -use super::{EncryptedVote, Tx, VotePayload, VoterProof}; use crate::{ - crypto::ed25519::{PublicKey, Signature}, utils::{read_array, read_be_u32, read_be_u64, read_be_u8}, + EncryptedVote, Tx, VotePayload, VoterProof, }; /// Jörmungandr tx fragment tag. @@ -214,129 +214,129 @@ impl Tx { } } -#[cfg(test)] -mod tests { - use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, Strategy}; - use test_strategy::proptest; - - use super::*; - use crate::crypto::ed25519::PrivateKey; - - impl Arbitrary for Tx { - type Parameters = (); - type Strategy = BoxedStrategy; - - fn arbitrary_with((): Self::Parameters) -> Self::Strategy { - any::<( - [u8; 32], - u8, - VotePayload, - PrivateKey, - [u8; Signature::BYTES_SIZE], - )>() - .prop_map( - |(vote_plan_id, proposal_index, vote, sk, signature_bytes)| { - Tx { - vote_plan_id, - proposal_index, - vote, - public_key: sk.public_key(), - signature: Signature::from_bytes(&signature_bytes), - } - }, - ) - .boxed() - } - } - - impl Arbitrary for VotePayload { - type Parameters = (); - type Strategy = BoxedStrategy; - - fn arbitrary_with((): Self::Parameters) -> Self::Strategy { - any::() - .prop_flat_map(|b| { - if b { - any::().prop_map(VotePayload::Public).boxed() - } else { - any::<(u8, u8)>() - .prop_flat_map(|(s1, s2)| { - any_with::<(EncryptedVote, VoterProof)>((s1.into(), s2.into())) - .prop_map(|(v, p)| VotePayload::Private(v, p)) - }) - .boxed() - } - }) - .boxed() - } - } - - #[proptest] - fn tx_to_bytes_from_bytes_test(t1: Tx) { - let bytes = t1.to_bytes(); - - let mut reader = bytes.as_slice(); - - let size = read_be_u32(&mut reader).unwrap(); - assert_eq!(size as usize, bytes.len() - 4); - - let padding_tag = read_be_u8(&mut reader).unwrap(); - assert_eq!(padding_tag, PADDING_TAG); - - let fragment_tag = read_be_u8(&mut reader).unwrap(); - assert_eq!(fragment_tag, FRAGMENT_TAG); - - let vote_plan_id = read_array(&mut reader).unwrap(); - assert_eq!(vote_plan_id, t1.vote_plan_id); - - let proposal_index = read_be_u8(&mut reader).unwrap(); - assert_eq!(proposal_index, t1.proposal_index); - - let vote_tag = read_be_u8(&mut reader).unwrap(); - assert!(vote_tag == PUBLIC_VOTE_TAG || vote_tag == PRIVATE_VOTE_TAG); - match vote_tag { - PUBLIC_VOTE_TAG => { - let vote = read_be_u8(&mut reader).unwrap(); - assert_eq!(VotePayload::Public(vote), t1.vote); - }, - PRIVATE_VOTE_TAG => { - let size = read_be_u8(&mut reader).unwrap(); - let vote = EncryptedVote::from_bytes(&mut reader, size.into()).unwrap(); - let size = read_be_u8(&mut reader).unwrap(); - let proof = VoterProof::from_bytes(&mut reader, size.into()).unwrap(); - assert_eq!(VotePayload::Private(vote, proof), t1.vote); - }, - _ => {}, - } - - let block_date = read_be_u64(&mut reader).unwrap(); - assert_eq!(block_date, 0); - - let inputs_amount = read_be_u8(&mut reader).unwrap(); - assert_eq!(inputs_amount, NUMBER_OF_INPUTS); - - let outputs_amount = read_be_u8(&mut reader).unwrap(); - assert_eq!(outputs_amount, NUMBER_OF_OUTPUTS); - - let input_tag = read_be_u8(&mut reader).unwrap(); - assert_eq!(input_tag, INPUT_TAG); - - let value = read_be_u64(&mut reader).unwrap(); - assert_eq!(value, 0); - - let public_key = read_array(&mut reader).unwrap(); - assert_eq!(PublicKey::from_bytes(&public_key).unwrap(), t1.public_key); - - let witness_tag = read_be_u8(&mut reader).unwrap(); - assert_eq!(witness_tag, WITNESS_TAG); - - let nonce = read_be_u32(&mut reader).unwrap(); - assert_eq!(nonce, 0); - - let signature = read_array(&mut reader).unwrap(); - assert_eq!(Signature::from_bytes(&signature), t1.signature); - - let t2 = Tx::from_bytes(&mut bytes.as_slice()).unwrap(); - assert_eq!(t1, t2); - } -} +// #[cfg(test)] +// mod tests { +// use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, Strategy}; +// use test_strategy::proptest; + +// use super::*; +// use crate::crypto::ed25519::PrivateKey; + +// impl Arbitrary for Tx { +// type Parameters = (); +// type Strategy = BoxedStrategy; + +// fn arbitrary_with((): Self::Parameters) -> Self::Strategy { +// any::<( +// [u8; 32], +// u8, +// VotePayload, +// PrivateKey, +// [u8; Signature::BYTES_SIZE], +// )>() +// .prop_map( +// |(vote_plan_id, proposal_index, vote, sk, signature_bytes)| { +// Tx { +// vote_plan_id, +// proposal_index, +// vote, +// public_key: sk.public_key(), +// signature: Signature::from_bytes(&signature_bytes), +// } +// }, +// ) +// .boxed() +// } +// } + +// impl Arbitrary for VotePayload { +// type Parameters = (); +// type Strategy = BoxedStrategy; + +// fn arbitrary_with((): Self::Parameters) -> Self::Strategy { +// any::() +// .prop_flat_map(|b| { +// if b { +// any::().prop_map(VotePayload::Public).boxed() +// } else { +// any::<(u8, u8)>() +// .prop_flat_map(|(s1, s2)| { +// any_with::<(EncryptedVote, VoterProof)>((s1.into(), +// s2.into())) .prop_map(|(v, p)| +// VotePayload::Private(v, p)) }) +// .boxed() +// } +// }) +// .boxed() +// } +// } + +// #[proptest] +// fn tx_to_bytes_from_bytes_test(t1: Tx) { +// let bytes = t1.to_bytes(); + +// let mut reader = bytes.as_slice(); + +// let size = read_be_u32(&mut reader).unwrap(); +// assert_eq!(size as usize, bytes.len() - 4); + +// let padding_tag = read_be_u8(&mut reader).unwrap(); +// assert_eq!(padding_tag, PADDING_TAG); + +// let fragment_tag = read_be_u8(&mut reader).unwrap(); +// assert_eq!(fragment_tag, FRAGMENT_TAG); + +// let vote_plan_id = read_array(&mut reader).unwrap(); +// assert_eq!(vote_plan_id, t1.vote_plan_id); + +// let proposal_index = read_be_u8(&mut reader).unwrap(); +// assert_eq!(proposal_index, t1.proposal_index); + +// let vote_tag = read_be_u8(&mut reader).unwrap(); +// assert!(vote_tag == PUBLIC_VOTE_TAG || vote_tag == PRIVATE_VOTE_TAG); +// match vote_tag { +// PUBLIC_VOTE_TAG => { +// let vote = read_be_u8(&mut reader).unwrap(); +// assert_eq!(VotePayload::Public(vote), t1.vote); +// }, +// PRIVATE_VOTE_TAG => { +// let size = read_be_u8(&mut reader).unwrap(); +// let vote = EncryptedVote::from_bytes(&mut reader, +// size.into()).unwrap(); let size = read_be_u8(&mut reader).unwrap(); +// let proof = VoterProof::from_bytes(&mut reader, size.into()).unwrap(); +// assert_eq!(VotePayload::Private(vote, proof), t1.vote); +// }, +// _ => {}, +// } + +// let block_date = read_be_u64(&mut reader).unwrap(); +// assert_eq!(block_date, 0); + +// let inputs_amount = read_be_u8(&mut reader).unwrap(); +// assert_eq!(inputs_amount, NUMBER_OF_INPUTS); + +// let outputs_amount = read_be_u8(&mut reader).unwrap(); +// assert_eq!(outputs_amount, NUMBER_OF_OUTPUTS); + +// let input_tag = read_be_u8(&mut reader).unwrap(); +// assert_eq!(input_tag, INPUT_TAG); + +// let value = read_be_u64(&mut reader).unwrap(); +// assert_eq!(value, 0); + +// let public_key = read_array(&mut reader).unwrap(); +// assert_eq!(PublicKey::from_bytes(&public_key).unwrap(), t1.public_key); + +// let witness_tag = read_be_u8(&mut reader).unwrap(); +// assert_eq!(witness_tag, WITNESS_TAG); + +// let nonce = read_be_u32(&mut reader).unwrap(); +// assert_eq!(nonce, 0); + +// let signature = read_array(&mut reader).unwrap(); +// assert_eq!(Signature::from_bytes(&signature), t1.signature); + +// let t2 = Tx::from_bytes(&mut bytes.as_slice()).unwrap(); +// assert_eq!(t1, t2); +// } +// } diff --git a/rust/jormungandr-vote-tx/src/lib.rs b/rust/jormungandr-vote-tx/src/lib.rs index e69de29bb2d..4ba0e9d886e 100644 --- a/rust/jormungandr-vote-tx/src/lib.rs +++ b/rust/jormungandr-vote-tx/src/lib.rs @@ -0,0 +1,339 @@ +//! A Jörmungandr transaction object structured following this [spec](https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/catalyst_voting/transaction/#v1-jormungandr) +//! +//! ```rust +//! use catalyst_voting::{ +//! crypto::{default_rng, ed25519::PrivateKey}, +//! txs::v1::Tx, +//! vote_protocol::committee::ElectionSecretKey, +//! }; +//! +//! let vote_plan_id = [0u8; 32]; +//! let proposal_index = 0u8; +//! +//! let voting_options = 3; +//! let choice = 1; +//! +//! let users_private_key = PrivateKey::random(&mut default_rng()); +//! let election_public_key = ElectionSecretKey::random_with_default_rng().public_key(); +//! +//! let public_tx = Tx::new_public( +//! vote_plan_id, +//! proposal_index, +//! voting_options, +//! choice, +//! &users_private_key, +//! ) +//! .unwrap(); +//! public_tx.verify_signature().unwrap(); +//! +//! let private_tx = Tx::new_private_with_default_rng( +//! vote_plan_id, +//! proposal_index, +//! voting_options, +//! choice, +//! &election_public_key, +//! &users_private_key, +//! ) +//! .unwrap(); +//! private_tx.verify_signature().unwrap(); +//! private_tx.verify_proof(&election_public_key).unwrap(); +//! ``` + +mod decoding; +mod utils; + +use anyhow::ensure; +use catalyst_voting::{ + crypto::{ + ed25519::{sign, verify_signature, PrivateKey, PublicKey, Signature}, + hash::{digest::Digest, Blake2b256Hasher, Blake2b512Hasher}, + rng::{default_rng, rand_core::CryptoRngCore}, + }, + vote_protocol::{ + committee::{ElectionPublicKey, ElectionSecretKey}, + voter::{ + decrypt_vote, encrypt_vote, + proof::{generate_voter_proof, verify_voter_proof, VoterProof, VoterProofCommitment}, + EncryptedVote, Vote, + }, + }, +}; + +/// A v1 (Jörmungandr) transaction struct +#[derive(Debug, Clone, PartialEq, Eq)] +#[must_use] +pub struct Tx { + /// Vote plan id + vote_plan_id: [u8; 32], + /// Proposal index + proposal_index: u8, + /// Vote + vote: VotePayload, + /// Public key + public_key: PublicKey, + /// Transaction signature + signature: Signature, +} + +/// Vote payload struct. +/// Contains all necessary information for the valid vote. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum VotePayload { + /// Public voting choice + Public(u8), + /// Private (encrypted) voting choice + Private(EncryptedVote, VoterProof), +} + +impl Tx { + /// Generate a new `Tx` with public vote. + /// + /// # Errors + /// - Invalid voting choice + pub fn new_public( + vote_plan_id: [u8; 32], proposal_index: u8, voting_options: u8, choice: u8, + users_private_key: &PrivateKey, + ) -> anyhow::Result { + let vote = VotePayload::new_public(choice, voting_options)?; + let signature = Self::sign(&vote_plan_id, proposal_index, &vote, users_private_key); + Ok(Self { + vote_plan_id, + proposal_index, + vote, + public_key: users_private_key.public_key(), + signature, + }) + } + + /// Generate a new `Tx` with public vote. + /// + /// # Errors + /// - Invalid voting choice + pub fn new_private( + vote_plan_id: [u8; 32], proposal_index: u8, voting_options: u8, choice: u8, + election_public_key: &ElectionPublicKey, users_private_key: &PrivateKey, rng: &mut R, + ) -> anyhow::Result { + let vote = VotePayload::new_private( + &vote_plan_id, + choice, + voting_options, + election_public_key, + rng, + )?; + let signature = Self::sign(&vote_plan_id, proposal_index, &vote, users_private_key); + + Ok(Self { + vote_plan_id, + proposal_index, + vote, + public_key: users_private_key.public_key(), + signature, + }) + } + + /// Generate a new `Tx` with public vote with `crypto::default_rng`. + /// + /// # Errors + /// - Invalid voting choice + pub fn new_private_with_default_rng( + vote_plan_id: [u8; 32], proposal_index: u8, voting_options: u8, choice: u8, + election_public_key: &ElectionPublicKey, users_private_key: &PrivateKey, + ) -> anyhow::Result { + Self::new_private( + vote_plan_id, + proposal_index, + voting_options, + choice, + election_public_key, + users_private_key, + &mut default_rng(), + ) + } + + /// Returns `true` if the vote is public + #[must_use] + pub fn is_public(&self) -> bool { + matches!(self.vote, VotePayload::Public(_)) + } + + /// Returns `true` if the vote is private + #[must_use] + pub fn is_private(&self) -> bool { + matches!(self.vote, VotePayload::Private(_, _)) + } + + /// Returns public voting choice. + /// + /// # Errors + /// - Not a public vote + pub fn public_choice(&self) -> anyhow::Result { + if let VotePayload::Public(choice) = &self.vote { + Ok(*choice) + } else { + Err(anyhow::anyhow!("Not a public vote")) + } + } + + /// Returns private voting choice. + /// + /// # Errors + /// - Not a private vote + #[allow(clippy::cast_possible_truncation)] + pub fn private_choice(&self, secret_key: &ElectionSecretKey) -> anyhow::Result { + if let VotePayload::Private(vote, _) = &self.vote { + let vote = decrypt_vote(vote, secret_key)?; + let choice = vote.choice() as u8; + Ok(choice) + } else { + Err(anyhow::anyhow!("Not a private vote")) + } + } + + /// Verify transaction signature + /// + /// # Errors + /// - Invalid signature + pub fn verify_signature(&self) -> anyhow::Result<()> { + let bytes = Self::bytes_to_sign( + &self.vote_plan_id, + self.proposal_index, + &self.vote, + &self.public_key, + ); + ensure!( + verify_signature(&self.public_key, &bytes, &self.signature), + "Invalid signature." + ); + Ok(()) + } + + /// Verify transaction proof of the private vote. + /// If vote is public it returns `Ok(())` + /// + /// # Errors + /// - Invalid proof + pub fn verify_proof(&self, election_public_key: &ElectionPublicKey) -> anyhow::Result<()> { + if let VotePayload::Private(encrypted_vote, proof) = &self.vote { + let vote_plan_id_hash = Blake2b512Hasher::new().chain_update(self.vote_plan_id); + let commitment = VoterProofCommitment::from_hash(vote_plan_id_hash); + ensure!( + verify_voter_proof( + encrypted_vote.clone(), + election_public_key, + &commitment, + proof, + ), + "Invalid proof." + ); + } + Ok(()) + } + + /// Generate transaction signature + fn sign( + vote_plan_id: &[u8; 32], proposal_index: u8, vote: &VotePayload, private_key: &PrivateKey, + ) -> Signature { + let bytes = Self::bytes_to_sign( + vote_plan_id, + proposal_index, + vote, + &private_key.public_key(), + ); + sign(private_key, &bytes) + } + + /// Generate bytes to be signed. + /// A Blake2b256 hash of the transaction body + fn bytes_to_sign( + vote_plan_id: &[u8; 32], proposal_index: u8, vote: &VotePayload, public_key: &PublicKey, + ) -> Vec { + let mut bytes = Vec::new(); + Self::tx_body_decode(vote_plan_id, proposal_index, vote, public_key, &mut bytes); + Blake2b256Hasher::new() + .chain_update(bytes.as_slice()) + .finalize() + .to_vec() + } +} + +#[allow(clippy::missing_docs_in_private_items)] +impl VotePayload { + fn new_public(choice: u8, proposal_voting_options: u8) -> anyhow::Result { + // Try to make a `Vote` just for applying underlying validation, which must be the same + // even for public vote + Vote::new(choice.into(), proposal_voting_options.into())?; + Ok(Self::Public(choice)) + } + + fn new_private( + vote_plan_id: &[u8; 32], choice: u8, proposal_voting_options: u8, + election_public_key: &ElectionPublicKey, rng: &mut R, + ) -> anyhow::Result { + let vote = Vote::new(choice.into(), proposal_voting_options.into())?; + + let (encrypted_vote, randomness) = encrypt_vote(&vote, election_public_key, rng); + + let vote_plan_id_hash = Blake2b512Hasher::new().chain_update(vote_plan_id); + let commitment = VoterProofCommitment::from_hash(vote_plan_id_hash); + + let voter_proof = generate_voter_proof( + &vote, + encrypted_vote.clone(), + randomness, + election_public_key, + &commitment, + rng, + )?; + + Ok(Self::Private(encrypted_vote, voter_proof)) + } +} + +// #[cfg(test)] +// mod tests { +// use test_strategy::proptest; + +// use super::*; +// use crate::{crypto::ed25519::PrivateKey, +// vote_protocol::committee::ElectionSecretKey}; + +// #[proptest] +// fn tx_test( +// vote_plan_id: [u8; 32], proposal_index: u8, #[strategy(1u8..5)] voting_options: +// u8, #[strategy(0..#voting_options)] choice: u8, users_private_key: PrivateKey, +// election_secret_key: ElectionSecretKey, +// ) { +// let election_public_key = election_secret_key.public_key(); + +// let tx = Tx::new_public( +// vote_plan_id, +// proposal_index, +// voting_options, +// choice, +// &users_private_key, +// ) +// .unwrap(); +// assert!(tx.is_public()); +// assert!(!tx.is_private()); +// tx.verify_signature().unwrap(); +// tx.verify_proof(&election_public_key).unwrap(); +// assert_eq!(tx.public_choice().unwrap(), choice); +// assert!(tx.private_choice(&election_secret_key).is_err()); + +// let tx = Tx::new_private_with_default_rng( +// vote_plan_id, +// proposal_index, +// voting_options, +// choice, +// &election_public_key, +// &users_private_key, +// ) +// .unwrap(); +// assert!(!tx.is_public()); +// assert!(tx.is_private()); +// tx.verify_signature().unwrap(); +// tx.verify_proof(&election_public_key).unwrap(); +// assert_eq!(tx.private_choice(&election_secret_key).unwrap(), choice); +// assert!(tx.public_choice().is_err()); +// } +// } diff --git a/rust/jormungandr-vote-tx/src/utils.rs b/rust/jormungandr-vote-tx/src/utils.rs new file mode 100644 index 00000000000..77a77950591 --- /dev/null +++ b/rust/jormungandr-vote-tx/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 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) +} From 99bbcbb3659fd3f1a250c4c3d7be0a57e5b43029 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 31 Oct 2024 13:11:32 +0200 Subject: [PATCH 03/10] update doc test --- rust/jormungandr-vote-tx/src/lib.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/rust/jormungandr-vote-tx/src/lib.rs b/rust/jormungandr-vote-tx/src/lib.rs index 4ba0e9d886e..ad45ed6c49c 100644 --- a/rust/jormungandr-vote-tx/src/lib.rs +++ b/rust/jormungandr-vote-tx/src/lib.rs @@ -2,10 +2,10 @@ //! //! ```rust //! use catalyst_voting::{ -//! crypto::{default_rng, ed25519::PrivateKey}, -//! txs::v1::Tx, +//! crypto::{ed25519::PrivateKey, rng::default_rng}, //! vote_protocol::committee::ElectionSecretKey, //! }; +//! use jormungandr_vote_tx::Tx; //! //! let vote_plan_id = [0u8; 32]; //! let proposal_index = 0u8; @@ -14,7 +14,8 @@ //! let choice = 1; //! //! let users_private_key = PrivateKey::random(&mut default_rng()); -//! let election_public_key = ElectionSecretKey::random_with_default_rng().public_key(); +//! let election_secret_key = ElectionSecretKey::random_with_default_rng(); +//! let election_public_key = election_secret_key.public_key(); //! //! let public_tx = Tx::new_public( //! vote_plan_id, @@ -25,6 +26,8 @@ //! ) //! .unwrap(); //! public_tx.verify_signature().unwrap(); +//! let tx_choice = public_tx.public_choice().unwrap(); +//! assert_eq!(tx_choice, choice); //! //! let private_tx = Tx::new_private_with_default_rng( //! vote_plan_id, @@ -37,6 +40,8 @@ //! .unwrap(); //! private_tx.verify_signature().unwrap(); //! private_tx.verify_proof(&election_public_key).unwrap(); +//! let tx_choice = private_tx.private_choice(&election_secret_key).unwrap(); +//! assert_eq!(tx_choice, choice); //! ``` mod decoding; From 7d008eeae2acb11c16621056100b52bcd5a8be04 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 31 Oct 2024 13:23:38 +0200 Subject: [PATCH 04/10] fix link --- rust/jormungandr-vote-tx/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/jormungandr-vote-tx/src/lib.rs b/rust/jormungandr-vote-tx/src/lib.rs index ad45ed6c49c..ec6b524274f 100644 --- a/rust/jormungandr-vote-tx/src/lib.rs +++ b/rust/jormungandr-vote-tx/src/lib.rs @@ -1,4 +1,4 @@ -//! A Jörmungandr transaction object structured following this [spec](https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/catalyst_voting/transaction/#v1-jormungandr) +//! A Jörmungandr transaction object structured following this [spec](https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/catalyst_voting/jorm/) //! //! ```rust //! use catalyst_voting::{ From 7c70da054c5098adfd817fbcfa66a6c6a6c1b963 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 31 Oct 2024 13:38:10 +0200 Subject: [PATCH 05/10] refactor Arbitrary impl --- rust/catalyst-voting/Cargo.toml | 5 +++++ rust/catalyst-voting/src/crypto/ed25519/mod.rs | 12 +++++++++--- rust/catalyst-voting/src/crypto/elgamal/mod.rs | 12 +++++++++--- .../src/crypto/group/ristretto255/mod.rs | 12 +++++++++--- .../src/crypto/zk_unit_vector/mod.rs | 17 ++++++++++++----- .../zk_unit_vector/randomness_announcements.rs | 4 ++-- .../src/vote_protocol/committee/mod.rs | 4 ++-- .../src/vote_protocol/voter/mod.rs | 9 +++++++-- .../src/vote_protocol/voter/proof.rs | 4 ++-- 9 files changed, 57 insertions(+), 22 deletions(-) diff --git a/rust/catalyst-voting/Cargo.toml b/rust/catalyst-voting/Cargo.toml index 2c23a9165dc..817a5776696 100644 --- a/rust/catalyst-voting/Cargo.toml +++ b/rust/catalyst-voting/Cargo.toml @@ -31,3 +31,8 @@ proptest = { version = "1.5.0" } # Potentially it could be replaced with using `proptest::property_test` attribute macro, # after this PR will be merged https://github.com/proptest-rs/proptest/pull/523 test-strategy = "0.4.0" + +[features] +# Feature which makes publicly available +# `proptest::prelude::Arbitrary` trait implementation for some types +proptest-arbitrary = [] diff --git a/rust/catalyst-voting/src/crypto/ed25519/mod.rs b/rust/catalyst-voting/src/crypto/ed25519/mod.rs index ca0557cd4db..74f867b93fa 100644 --- a/rust/catalyst-voting/src/crypto/ed25519/mod.rs +++ b/rust/catalyst-voting/src/crypto/ed25519/mod.rs @@ -46,10 +46,9 @@ pub fn verify_signature(pk: &PublicKey, msg: &[u8], sig: &Signature) -> bool { pk.0.verify_strict(msg, &sig.0).is_ok() } -#[cfg(test)] -mod tests { +#[cfg(any(test, feature = "proptest-arbitrary"))] +mod arbitrary_impl { use proptest::prelude::{any, Arbitrary, BoxedStrategy, Strategy}; - use test_strategy::proptest; use super::*; @@ -63,6 +62,13 @@ mod tests { .boxed() } } +} + +#[cfg(test)] +mod tests { + use test_strategy::proptest; + + use super::*; #[proptest] fn sign_test(private_key: PrivateKey, msg: Vec) { diff --git a/rust/catalyst-voting/src/crypto/elgamal/mod.rs b/rust/catalyst-voting/src/crypto/elgamal/mod.rs index 25b1920284c..565d4e545e7 100644 --- a/rust/catalyst-voting/src/crypto/elgamal/mod.rs +++ b/rust/catalyst-voting/src/crypto/elgamal/mod.rs @@ -66,13 +66,12 @@ impl Add<&Ciphertext> for &Ciphertext { } } -#[cfg(test)] -mod tests { +#[cfg(any(test, feature = "proptest-arbitrary"))] +mod arbitrary_impl { use proptest::{ arbitrary::any, prelude::{Arbitrary, BoxedStrategy, Strategy}, }; - use test_strategy::proptest; use super::*; @@ -86,6 +85,13 @@ mod tests { .boxed() } } +} + +#[cfg(test)] +mod tests { + use test_strategy::proptest; + + use super::*; #[proptest] fn ciphertext_add_test(e1: Scalar, e2: Scalar, e3: Scalar, e4: Scalar) { diff --git a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs index 1e66c947d88..145a92026f4 100644 --- a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs +++ b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs @@ -159,13 +159,12 @@ impl Sub<&GroupElement> for &GroupElement { } } -#[cfg(test)] -mod tests { +#[cfg(any(test, feature = "proptest-arbitrary"))] +mod arbitrary_impl { use proptest::{ arbitrary::any, prelude::{Arbitrary, BoxedStrategy, Strategy}, }; - use test_strategy::proptest; use super::*; @@ -188,6 +187,13 @@ mod tests { .boxed() } } +} + +#[cfg(test)] +mod tests { + use test_strategy::proptest; + + use super::*; #[proptest] fn scalar_arithmetic_tests(e1: Scalar, e2: Scalar, e3: Scalar) { 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 b63d6a9d7b7..89ba9ebab74 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs @@ -235,16 +235,14 @@ fn check_2( &right_1 + &right_2 == left } -#[cfg(test)] -mod tests { +#[cfg(any(test, feature = "proptest-arbitrary"))] +mod arbitrary_impl { use proptest::{ prelude::{any_with, Arbitrary, BoxedStrategy, Strategy}, sample::size_range, }; - use rand_core::OsRng; - use test_strategy::proptest; - use super::{super::elgamal::generate_public_key, *}; + use super::*; impl Arbitrary for UnitVectorProof { type Parameters = usize; @@ -263,6 +261,15 @@ mod tests { .boxed() } } +} + +#[cfg(test)] +mod tests { + use proptest::sample::size_range; + use rand_core::OsRng; + use test_strategy::proptest; + + use super::{super::elgamal::generate_public_key, *}; fn is_unit_vector(vector: &[Scalar]) -> bool { let ones = vector.iter().filter(|s| s == &&Scalar::one()).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 580a2c3852e..0c4715b012c 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 @@ -79,8 +79,8 @@ impl ResponseRandomness { } } -#[cfg(test)] -mod tests { +#[cfg(any(test, feature = "proptest-arbitrary"))] +mod arbitrary_impl { use proptest::{ arbitrary::any, prelude::{Arbitrary, BoxedStrategy, Strategy}, diff --git a/rust/catalyst-voting/src/vote_protocol/committee/mod.rs b/rust/catalyst-voting/src/vote_protocol/committee/mod.rs index dfefeacf1d4..50ba4088d79 100644 --- a/rust/catalyst-voting/src/vote_protocol/committee/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/committee/mod.rs @@ -36,8 +36,8 @@ impl ElectionSecretKey { #[derive(Debug, Clone, PartialEq, Eq)] pub struct ElectionPublicKey(pub(crate) GroupElement); -#[cfg(test)] -mod tests { +#[cfg(any(test, feature = "proptest-arbitrary"))] +mod arbitrary_impl { use proptest::prelude::{any, Arbitrary, BoxedStrategy, Strategy}; use super::*; diff --git a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index 44e8c641309..fdd1f2a04c7 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -141,8 +141,8 @@ pub fn decrypt_vote(vote: &EncryptedVote, secret_key: &ElectionSecretKey) -> any bail!("Invalid encrypted vote, not a valid unit vector.") } -#[cfg(test)] -mod tests { +#[cfg(any(test, feature = "proptest-arbitrary"))] +mod arbitrary_impl { use proptest::{ prelude::{any_with, Arbitrary, BoxedStrategy, Strategy}, sample::size_range, @@ -160,6 +160,11 @@ mod tests { .boxed() } } +} + +#[cfg(test)] +mod tests { + use super::*; #[test] fn vote_test() { diff --git a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs index cc85468c1b1..8ebf9ada50c 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs @@ -107,8 +107,8 @@ pub fn verify_voter_proof( verify_unit_vector_proof(&proof.0, encrypted_vote.0, &public_key.0, &commitment.0) } -#[cfg(test)] -mod tests { +#[cfg(any(test, feature = "proptest-arbitrary"))] +mod arbitrary_impl { use proptest::prelude::{any_with, Arbitrary, BoxedStrategy, Strategy}; use super::*; From 779dd52e7ad3b5f7bc041adaeffd0c46206c33c7 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 31 Oct 2024 13:53:51 +0200 Subject: [PATCH 06/10] refactor proptest::Arbitrary impl --- rust/catalyst-voting/Cargo.toml | 3 ++- rust/catalyst-voting/src/crypto/ed25519/mod.rs | 3 ++- rust/catalyst-voting/src/crypto/elgamal/mod.rs | 3 ++- rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs | 3 ++- rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs | 3 ++- .../src/crypto/zk_unit_vector/randomness_announcements.rs | 3 ++- rust/catalyst-voting/src/vote_protocol/committee/mod.rs | 3 ++- rust/catalyst-voting/src/vote_protocol/voter/mod.rs | 3 ++- rust/catalyst-voting/src/vote_protocol/voter/proof.rs | 3 ++- rust/jormungandr-vote-tx/Cargo.toml | 8 ++++++++ 10 files changed, 26 insertions(+), 9 deletions(-) diff --git a/rust/catalyst-voting/Cargo.toml b/rust/catalyst-voting/Cargo.toml index 817a5776696..85d5593ba5f 100644 --- a/rust/catalyst-voting/Cargo.toml +++ b/rust/catalyst-voting/Cargo.toml @@ -24,6 +24,7 @@ curve25519-dalek = { version = "4.1.3", features = ["digest", "rand_core"] } ed25519-dalek = { version = "2.1.1", features = ["rand_core"] } blake2b_simd = "1.0.2" rayon = "1.10.0" +proptest = { version = "1.5.0", optional = true } [dev-dependencies] criterion = "0.5.1" @@ -35,4 +36,4 @@ test-strategy = "0.4.0" [features] # Feature which makes publicly available # `proptest::prelude::Arbitrary` trait implementation for some types -proptest-arbitrary = [] +proptest-arbitrary = ["dep:proptest"] diff --git a/rust/catalyst-voting/src/crypto/ed25519/mod.rs b/rust/catalyst-voting/src/crypto/ed25519/mod.rs index 74f867b93fa..927447fd249 100644 --- a/rust/catalyst-voting/src/crypto/ed25519/mod.rs +++ b/rust/catalyst-voting/src/crypto/ed25519/mod.rs @@ -47,10 +47,11 @@ pub fn verify_signature(pk: &PublicKey, msg: &[u8], sig: &Signature) -> bool { } #[cfg(any(test, feature = "proptest-arbitrary"))] +#[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::prelude::{any, Arbitrary, BoxedStrategy, Strategy}; - use super::*; + use super::{PrivateKey, SigningKey}; impl Arbitrary for PrivateKey { type Parameters = (); diff --git a/rust/catalyst-voting/src/crypto/elgamal/mod.rs b/rust/catalyst-voting/src/crypto/elgamal/mod.rs index 565d4e545e7..baf8cbda17b 100644 --- a/rust/catalyst-voting/src/crypto/elgamal/mod.rs +++ b/rust/catalyst-voting/src/crypto/elgamal/mod.rs @@ -67,13 +67,14 @@ impl Add<&Ciphertext> for &Ciphertext { } #[cfg(any(test, feature = "proptest-arbitrary"))] +#[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::{ arbitrary::any, prelude::{Arbitrary, BoxedStrategy, Strategy}, }; - use super::*; + use super::{Ciphertext, GroupElement}; impl Arbitrary for Ciphertext { type Parameters = (); diff --git a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs index 145a92026f4..67ede81809f 100644 --- a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs +++ b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs @@ -160,13 +160,14 @@ impl Sub<&GroupElement> for &GroupElement { } #[cfg(any(test, feature = "proptest-arbitrary"))] +#[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::{ arbitrary::any, prelude::{Arbitrary, BoxedStrategy, Strategy}, }; - use super::*; + use super::{GroupElement, Mul, Scalar}; impl Arbitrary for Scalar { type Parameters = (); 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 89ba9ebab74..7fabfc82746 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs @@ -236,13 +236,14 @@ fn check_2( } #[cfg(any(test, feature = "proptest-arbitrary"))] +#[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::{ prelude::{any_with, Arbitrary, BoxedStrategy, Strategy}, sample::size_range, }; - use super::*; + use super::{Announcement, Ciphertext, ResponseRandomness, Scalar, UnitVectorProof}; impl Arbitrary for UnitVectorProof { type Parameters = usize; 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 0c4715b012c..a277ea0696f 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 @@ -80,13 +80,14 @@ impl ResponseRandomness { } #[cfg(any(test, feature = "proptest-arbitrary"))] +#[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::{ arbitrary::any, prelude::{Arbitrary, BoxedStrategy, Strategy}, }; - use super::*; + use super::{Announcement, BlindingRandomness, GroupElement, ResponseRandomness, Scalar}; impl Arbitrary for BlindingRandomness { type Parameters = (); diff --git a/rust/catalyst-voting/src/vote_protocol/committee/mod.rs b/rust/catalyst-voting/src/vote_protocol/committee/mod.rs index 50ba4088d79..9287ee2ba1b 100644 --- a/rust/catalyst-voting/src/vote_protocol/committee/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/committee/mod.rs @@ -37,10 +37,11 @@ impl ElectionSecretKey { pub struct ElectionPublicKey(pub(crate) GroupElement); #[cfg(any(test, feature = "proptest-arbitrary"))] +#[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::prelude::{any, Arbitrary, BoxedStrategy, Strategy}; - use super::*; + use super::{ElectionSecretKey, Scalar}; impl Arbitrary for ElectionSecretKey { type Parameters = (); diff --git a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index fdd1f2a04c7..f1fe07d1faa 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -142,13 +142,14 @@ pub fn decrypt_vote(vote: &EncryptedVote, secret_key: &ElectionSecretKey) -> any } #[cfg(any(test, feature = "proptest-arbitrary"))] +#[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::{ prelude::{any_with, Arbitrary, BoxedStrategy, Strategy}, sample::size_range, }; - use super::*; + use super::{Ciphertext, EncryptedVote}; impl Arbitrary for EncryptedVote { type Parameters = usize; diff --git a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs index 8ebf9ada50c..d580dcf8532 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs @@ -108,10 +108,11 @@ pub fn verify_voter_proof( } #[cfg(any(test, feature = "proptest-arbitrary"))] +#[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::prelude::{any_with, Arbitrary, BoxedStrategy, Strategy}; - use super::*; + use super::{UnitVectorProof, VoterProof}; impl Arbitrary for VoterProof { type Parameters = usize; diff --git a/rust/jormungandr-vote-tx/Cargo.toml b/rust/jormungandr-vote-tx/Cargo.toml index 9bccdcabab6..50cd61eeb2a 100644 --- a/rust/jormungandr-vote-tx/Cargo.toml +++ b/rust/jormungandr-vote-tx/Cargo.toml @@ -14,7 +14,15 @@ workspace = true catalyst-voting = { path = "../catalyst-voting" } anyhow = "1.0.89" +[dev-dependencies] +catalyst-voting = { path = "../catalyst-voting", features = ["proptest-arbitrary"] } + proptest = { version = "1.5.0" } # Potentially it could be replaced with using `proptest::property_test` attribute macro, # after this PR will be merged https://github.com/proptest-rs/proptest/pull/523 test-strategy = "0.4.0" + +[features] +# Feature which makes publicly available +# `proptest::prelude::Arbitrary` trait implementation for some types +proptest-arbitrary = ["catalyst-voting/proptest-arbitrary"] From 414ff2197707b80842a6748282de40788578a020 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 31 Oct 2024 14:01:14 +0200 Subject: [PATCH 07/10] wip --- rust/jormungandr-vote-tx/Cargo.toml | 3 +- rust/jormungandr-vote-tx/src/decoding.rs | 201 +++++++++-------------- rust/jormungandr-vote-tx/src/lib.rs | 158 ++++++++++++------ 3 files changed, 186 insertions(+), 176 deletions(-) diff --git a/rust/jormungandr-vote-tx/Cargo.toml b/rust/jormungandr-vote-tx/Cargo.toml index 50cd61eeb2a..d893854d14f 100644 --- a/rust/jormungandr-vote-tx/Cargo.toml +++ b/rust/jormungandr-vote-tx/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] catalyst-voting = { path = "../catalyst-voting" } anyhow = "1.0.89" +proptest = { version = "1.5.0", optional = true } [dev-dependencies] catalyst-voting = { path = "../catalyst-voting", features = ["proptest-arbitrary"] } @@ -25,4 +26,4 @@ test-strategy = "0.4.0" [features] # Feature which makes publicly available # `proptest::prelude::Arbitrary` trait implementation for some types -proptest-arbitrary = ["catalyst-voting/proptest-arbitrary"] +proptest-arbitrary = ["catalyst-voting/proptest-arbitrary", "dep:proptest"] diff --git a/rust/jormungandr-vote-tx/src/decoding.rs b/rust/jormungandr-vote-tx/src/decoding.rs index 228be697780..3eb78bdae9e 100644 --- a/rust/jormungandr-vote-tx/src/decoding.rs +++ b/rust/jormungandr-vote-tx/src/decoding.rs @@ -214,129 +214,78 @@ impl Tx { } } -// #[cfg(test)] -// mod tests { -// use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, Strategy}; -// use test_strategy::proptest; - -// use super::*; -// use crate::crypto::ed25519::PrivateKey; - -// impl Arbitrary for Tx { -// type Parameters = (); -// type Strategy = BoxedStrategy; - -// fn arbitrary_with((): Self::Parameters) -> Self::Strategy { -// any::<( -// [u8; 32], -// u8, -// VotePayload, -// PrivateKey, -// [u8; Signature::BYTES_SIZE], -// )>() -// .prop_map( -// |(vote_plan_id, proposal_index, vote, sk, signature_bytes)| { -// Tx { -// vote_plan_id, -// proposal_index, -// vote, -// public_key: sk.public_key(), -// signature: Signature::from_bytes(&signature_bytes), -// } -// }, -// ) -// .boxed() -// } -// } - -// impl Arbitrary for VotePayload { -// type Parameters = (); -// type Strategy = BoxedStrategy; - -// fn arbitrary_with((): Self::Parameters) -> Self::Strategy { -// any::() -// .prop_flat_map(|b| { -// if b { -// any::().prop_map(VotePayload::Public).boxed() -// } else { -// any::<(u8, u8)>() -// .prop_flat_map(|(s1, s2)| { -// any_with::<(EncryptedVote, VoterProof)>((s1.into(), -// s2.into())) .prop_map(|(v, p)| -// VotePayload::Private(v, p)) }) -// .boxed() -// } -// }) -// .boxed() -// } -// } - -// #[proptest] -// fn tx_to_bytes_from_bytes_test(t1: Tx) { -// let bytes = t1.to_bytes(); - -// let mut reader = bytes.as_slice(); - -// let size = read_be_u32(&mut reader).unwrap(); -// assert_eq!(size as usize, bytes.len() - 4); - -// let padding_tag = read_be_u8(&mut reader).unwrap(); -// assert_eq!(padding_tag, PADDING_TAG); - -// let fragment_tag = read_be_u8(&mut reader).unwrap(); -// assert_eq!(fragment_tag, FRAGMENT_TAG); - -// let vote_plan_id = read_array(&mut reader).unwrap(); -// assert_eq!(vote_plan_id, t1.vote_plan_id); - -// let proposal_index = read_be_u8(&mut reader).unwrap(); -// assert_eq!(proposal_index, t1.proposal_index); - -// let vote_tag = read_be_u8(&mut reader).unwrap(); -// assert!(vote_tag == PUBLIC_VOTE_TAG || vote_tag == PRIVATE_VOTE_TAG); -// match vote_tag { -// PUBLIC_VOTE_TAG => { -// let vote = read_be_u8(&mut reader).unwrap(); -// assert_eq!(VotePayload::Public(vote), t1.vote); -// }, -// PRIVATE_VOTE_TAG => { -// let size = read_be_u8(&mut reader).unwrap(); -// let vote = EncryptedVote::from_bytes(&mut reader, -// size.into()).unwrap(); let size = read_be_u8(&mut reader).unwrap(); -// let proof = VoterProof::from_bytes(&mut reader, size.into()).unwrap(); -// assert_eq!(VotePayload::Private(vote, proof), t1.vote); -// }, -// _ => {}, -// } - -// let block_date = read_be_u64(&mut reader).unwrap(); -// assert_eq!(block_date, 0); - -// let inputs_amount = read_be_u8(&mut reader).unwrap(); -// assert_eq!(inputs_amount, NUMBER_OF_INPUTS); - -// let outputs_amount = read_be_u8(&mut reader).unwrap(); -// assert_eq!(outputs_amount, NUMBER_OF_OUTPUTS); - -// let input_tag = read_be_u8(&mut reader).unwrap(); -// assert_eq!(input_tag, INPUT_TAG); - -// let value = read_be_u64(&mut reader).unwrap(); -// assert_eq!(value, 0); - -// let public_key = read_array(&mut reader).unwrap(); -// assert_eq!(PublicKey::from_bytes(&public_key).unwrap(), t1.public_key); - -// let witness_tag = read_be_u8(&mut reader).unwrap(); -// assert_eq!(witness_tag, WITNESS_TAG); - -// let nonce = read_be_u32(&mut reader).unwrap(); -// assert_eq!(nonce, 0); - -// let signature = read_array(&mut reader).unwrap(); -// assert_eq!(Signature::from_bytes(&signature), t1.signature); - -// let t2 = Tx::from_bytes(&mut bytes.as_slice()).unwrap(); -// assert_eq!(t1, t2); -// } -// } +#[cfg(test)] +mod tests { + use test_strategy::proptest; + + use super::*; + + #[proptest] + fn tx_to_bytes_from_bytes_test(t1: Tx) { + let bytes = t1.to_bytes(); + + let mut reader = bytes.as_slice(); + + let size = read_be_u32(&mut reader).unwrap(); + assert_eq!(size as usize, bytes.len() - 4); + + let padding_tag = read_be_u8(&mut reader).unwrap(); + assert_eq!(padding_tag, PADDING_TAG); + + let fragment_tag = read_be_u8(&mut reader).unwrap(); + assert_eq!(fragment_tag, FRAGMENT_TAG); + + let vote_plan_id = read_array(&mut reader).unwrap(); + assert_eq!(vote_plan_id, t1.vote_plan_id); + + let proposal_index = read_be_u8(&mut reader).unwrap(); + assert_eq!(proposal_index, t1.proposal_index); + + let vote_tag = read_be_u8(&mut reader).unwrap(); + assert!(vote_tag == PUBLIC_VOTE_TAG || vote_tag == PRIVATE_VOTE_TAG); + match vote_tag { + PUBLIC_VOTE_TAG => { + let vote = read_be_u8(&mut reader).unwrap(); + assert_eq!(VotePayload::Public(vote), t1.vote); + }, + PRIVATE_VOTE_TAG => { + let size = read_be_u8(&mut reader).unwrap(); + let vote = EncryptedVote::from_bytes(&mut reader, size.into()).unwrap(); + let size = read_be_u8(&mut reader).unwrap(); + let proof = VoterProof::from_bytes(&mut reader, size.into()).unwrap(); + assert_eq!(VotePayload::Private(vote, proof), t1.vote); + }, + _ => {}, + } + + let block_date = read_be_u64(&mut reader).unwrap(); + assert_eq!(block_date, 0); + + let inputs_amount = read_be_u8(&mut reader).unwrap(); + assert_eq!(inputs_amount, NUMBER_OF_INPUTS); + + let outputs_amount = read_be_u8(&mut reader).unwrap(); + assert_eq!(outputs_amount, NUMBER_OF_OUTPUTS); + + let input_tag = read_be_u8(&mut reader).unwrap(); + assert_eq!(input_tag, INPUT_TAG); + + let value = read_be_u64(&mut reader).unwrap(); + assert_eq!(value, 0); + + let public_key = read_array(&mut reader).unwrap(); + assert_eq!(PublicKey::from_bytes(&public_key).unwrap(), t1.public_key); + + let witness_tag = read_be_u8(&mut reader).unwrap(); + assert_eq!(witness_tag, WITNESS_TAG); + + let nonce = read_be_u32(&mut reader).unwrap(); + assert_eq!(nonce, 0); + + let signature = read_array(&mut reader).unwrap(); + assert_eq!(Signature::from_bytes(&signature), t1.signature); + + let t2 = Tx::from_bytes(&mut bytes.as_slice()).unwrap(); + assert_eq!(t1, t2); + } +} diff --git a/rust/jormungandr-vote-tx/src/lib.rs b/rust/jormungandr-vote-tx/src/lib.rs index ec6b524274f..fa06e477180 100644 --- a/rust/jormungandr-vote-tx/src/lib.rs +++ b/rust/jormungandr-vote-tx/src/lib.rs @@ -1,4 +1,5 @@ -//! A Jörmungandr transaction object structured following this [spec](https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/catalyst_voting/jorm/) +//! A Jörmungandr transaction object structured following this +//! [spec](https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/catalyst_voting/jorm/) //! //! ```rust //! use catalyst_voting::{ @@ -294,51 +295,110 @@ impl VotePayload { } } -// #[cfg(test)] -// mod tests { -// use test_strategy::proptest; - -// use super::*; -// use crate::{crypto::ed25519::PrivateKey, -// vote_protocol::committee::ElectionSecretKey}; - -// #[proptest] -// fn tx_test( -// vote_plan_id: [u8; 32], proposal_index: u8, #[strategy(1u8..5)] voting_options: -// u8, #[strategy(0..#voting_options)] choice: u8, users_private_key: PrivateKey, -// election_secret_key: ElectionSecretKey, -// ) { -// let election_public_key = election_secret_key.public_key(); - -// let tx = Tx::new_public( -// vote_plan_id, -// proposal_index, -// voting_options, -// choice, -// &users_private_key, -// ) -// .unwrap(); -// assert!(tx.is_public()); -// assert!(!tx.is_private()); -// tx.verify_signature().unwrap(); -// tx.verify_proof(&election_public_key).unwrap(); -// assert_eq!(tx.public_choice().unwrap(), choice); -// assert!(tx.private_choice(&election_secret_key).is_err()); - -// let tx = Tx::new_private_with_default_rng( -// vote_plan_id, -// proposal_index, -// voting_options, -// choice, -// &election_public_key, -// &users_private_key, -// ) -// .unwrap(); -// assert!(!tx.is_public()); -// assert!(tx.is_private()); -// tx.verify_signature().unwrap(); -// tx.verify_proof(&election_public_key).unwrap(); -// assert_eq!(tx.private_choice(&election_secret_key).unwrap(), choice); -// assert!(tx.public_choice().is_err()); -// } -// } +#[cfg(any(test, feature = "proptest-arbitrary"))] +#[allow(missing_docs, clippy::missing_docs_in_private_items)] +mod arbitrary_impl { + use catalyst_voting::crypto::ed25519::PrivateKey; + use proptest::prelude::{any, any_with, Arbitrary, BoxedStrategy, Strategy}; + + use super::{EncryptedVote, Signature, Tx, VotePayload, VoterProof}; + + impl Arbitrary for Tx { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + any::<( + [u8; 32], + u8, + VotePayload, + PrivateKey, + [u8; Signature::BYTES_SIZE], + )>() + .prop_map( + |(vote_plan_id, proposal_index, vote, sk, signature_bytes)| { + Tx { + vote_plan_id, + proposal_index, + vote, + public_key: sk.public_key(), + signature: Signature::from_bytes(&signature_bytes), + } + }, + ) + .boxed() + } + } + + impl Arbitrary for VotePayload { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + any::() + .prop_flat_map(|b| { + if b { + any::().prop_map(VotePayload::Public).boxed() + } else { + any::<(u8, u8)>() + .prop_flat_map(|(s1, s2)| { + any_with::<(EncryptedVote, VoterProof)>((s1.into(), s2.into())) + .prop_map(|(v, p)| VotePayload::Private(v, p)) + }) + .boxed() + } + }) + .boxed() + } + } +} + +#[cfg(test)] +mod tests { + use catalyst_voting::{ + crypto::ed25519::PrivateKey, vote_protocol::committee::ElectionSecretKey, + }; + use test_strategy::proptest; + + use super::*; + + #[proptest] + fn tx_test( + vote_plan_id: [u8; 32], proposal_index: u8, #[strategy(1u8..5)] voting_options: u8, + #[strategy(0..#voting_options)] choice: u8, users_private_key: PrivateKey, + election_secret_key: ElectionSecretKey, + ) { + let election_public_key = election_secret_key.public_key(); + + let tx = Tx::new_public( + vote_plan_id, + proposal_index, + voting_options, + choice, + &users_private_key, + ) + .unwrap(); + assert!(tx.is_public()); + assert!(!tx.is_private()); + tx.verify_signature().unwrap(); + tx.verify_proof(&election_public_key).unwrap(); + assert_eq!(tx.public_choice().unwrap(), choice); + assert!(tx.private_choice(&election_secret_key).is_err()); + + let tx = Tx::new_private_with_default_rng( + vote_plan_id, + proposal_index, + voting_options, + choice, + &election_public_key, + &users_private_key, + ) + .unwrap(); + assert!(!tx.is_public()); + assert!(tx.is_private()); + tx.verify_signature().unwrap(); + tx.verify_proof(&election_public_key).unwrap(); + assert_eq!(tx.private_choice(&election_secret_key).unwrap(), choice); + assert!(tx.public_choice().is_err()); + } +} From b8239d941f5cd1e372226bab4d86b157f4fcd608 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 31 Oct 2024 14:02:19 +0200 Subject: [PATCH 08/10] fix earthfile --- rust/Earthfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/Earthfile b/rust/Earthfile index 0339348a2c0..714d9df7183 100644 --- a/rust/Earthfile +++ b/rust/Earthfile @@ -10,7 +10,7 @@ COPY_SRC: .cargo .config \ c509-certificate \ cardano-chain-follower \ - catalyst-voting \ + catalyst-voting jormungandr-vote-tx \ cbork cbork-abnf-parser cbork-cddl-parser \ hermes-ipfs \ . @@ -53,7 +53,7 @@ build: --cmd="/scripts/std_build.py" \ --args1="--libs=c509-certificate --libs=cardano-chain-follower --libs=hermes-ipfs" \ --args2="--libs=cbork-cddl-parser --libs=cbork-abnf-parser" \ - --args3="--libs=catalyst-voting" \ + --args3="--libs=catalyst-voting --libs=jormungandr-vote-tx" \ --args4="--bins=cbork/cbork" \ --args5="--cov_report=$HOME/build/coverage-report.info" \ --output="release/[^\./]+" \ From afeecc357dc68732856398c30b3cd7505261c4dd Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 31 Oct 2024 14:43:33 +0200 Subject: [PATCH 09/10] fix cargo deny --- rust/jormungandr-vote-tx/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/jormungandr-vote-tx/Cargo.toml b/rust/jormungandr-vote-tx/Cargo.toml index d893854d14f..ebb98f2095a 100644 --- a/rust/jormungandr-vote-tx/Cargo.toml +++ b/rust/jormungandr-vote-tx/Cargo.toml @@ -11,12 +11,12 @@ license.workspace = true workspace = true [dependencies] -catalyst-voting = { path = "../catalyst-voting" } +catalyst-voting = { version = "0.0.1", path = "../catalyst-voting" } anyhow = "1.0.89" proptest = { version = "1.5.0", optional = true } [dev-dependencies] -catalyst-voting = { path = "../catalyst-voting", features = ["proptest-arbitrary"] } +catalyst-voting = { version = "0.0.1", path = "../catalyst-voting", features = ["proptest-arbitrary"] } proptest = { version = "1.5.0" } # Potentially it could be replaced with using `proptest::property_test` attribute macro, From 43614603bc675cc1c83e2e204e0d858d901ba345 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 31 Oct 2024 15:46:38 +0200 Subject: [PATCH 10/10] remove feature flags --- rust/catalyst-voting/Cargo.toml | 8 +------- rust/catalyst-voting/src/crypto/ed25519/mod.rs | 1 - rust/catalyst-voting/src/crypto/elgamal/mod.rs | 1 - .../src/crypto/group/ristretto255/mod.rs | 1 - .../catalyst-voting/src/crypto/zk_unit_vector/mod.rs | 1 - .../zk_unit_vector/randomness_announcements.rs | 1 - .../src/vote_protocol/committee/mod.rs | 1 - rust/catalyst-voting/src/vote_protocol/voter/mod.rs | 1 - .../catalyst-voting/src/vote_protocol/voter/proof.rs | 1 - rust/jormungandr-vote-tx/Cargo.toml | 12 ++---------- rust/jormungandr-vote-tx/src/lib.rs | 1 - 11 files changed, 3 insertions(+), 26 deletions(-) diff --git a/rust/catalyst-voting/Cargo.toml b/rust/catalyst-voting/Cargo.toml index 85d5593ba5f..ee48ab65c31 100644 --- a/rust/catalyst-voting/Cargo.toml +++ b/rust/catalyst-voting/Cargo.toml @@ -24,16 +24,10 @@ curve25519-dalek = { version = "4.1.3", features = ["digest", "rand_core"] } ed25519-dalek = { version = "2.1.1", features = ["rand_core"] } blake2b_simd = "1.0.2" rayon = "1.10.0" -proptest = { version = "1.5.0", optional = true } +proptest = { version = "1.5.0" } [dev-dependencies] criterion = "0.5.1" -proptest = { version = "1.5.0" } # Potentially it could be replaced with using `proptest::property_test` attribute macro, # after this PR will be merged https://github.com/proptest-rs/proptest/pull/523 test-strategy = "0.4.0" - -[features] -# Feature which makes publicly available -# `proptest::prelude::Arbitrary` trait implementation for some types -proptest-arbitrary = ["dep:proptest"] diff --git a/rust/catalyst-voting/src/crypto/ed25519/mod.rs b/rust/catalyst-voting/src/crypto/ed25519/mod.rs index 927447fd249..ed16c6df39a 100644 --- a/rust/catalyst-voting/src/crypto/ed25519/mod.rs +++ b/rust/catalyst-voting/src/crypto/ed25519/mod.rs @@ -46,7 +46,6 @@ pub fn verify_signature(pk: &PublicKey, msg: &[u8], sig: &Signature) -> bool { pk.0.verify_strict(msg, &sig.0).is_ok() } -#[cfg(any(test, feature = "proptest-arbitrary"))] #[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::prelude::{any, Arbitrary, BoxedStrategy, Strategy}; diff --git a/rust/catalyst-voting/src/crypto/elgamal/mod.rs b/rust/catalyst-voting/src/crypto/elgamal/mod.rs index baf8cbda17b..4913476e6a4 100644 --- a/rust/catalyst-voting/src/crypto/elgamal/mod.rs +++ b/rust/catalyst-voting/src/crypto/elgamal/mod.rs @@ -66,7 +66,6 @@ impl Add<&Ciphertext> for &Ciphertext { } } -#[cfg(any(test, feature = "proptest-arbitrary"))] #[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::{ diff --git a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs index 67ede81809f..e8963224a71 100644 --- a/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs +++ b/rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs @@ -159,7 +159,6 @@ impl Sub<&GroupElement> for &GroupElement { } } -#[cfg(any(test, feature = "proptest-arbitrary"))] #[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::{ 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 7fabfc82746..b9775b45c23 100644 --- a/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs +++ b/rust/catalyst-voting/src/crypto/zk_unit_vector/mod.rs @@ -235,7 +235,6 @@ fn check_2( &right_1 + &right_2 == left } -#[cfg(any(test, feature = "proptest-arbitrary"))] #[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::{ 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 a277ea0696f..19e0d14ed38 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 @@ -79,7 +79,6 @@ impl ResponseRandomness { } } -#[cfg(any(test, feature = "proptest-arbitrary"))] #[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::{ diff --git a/rust/catalyst-voting/src/vote_protocol/committee/mod.rs b/rust/catalyst-voting/src/vote_protocol/committee/mod.rs index 9287ee2ba1b..9a53f7d75de 100644 --- a/rust/catalyst-voting/src/vote_protocol/committee/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/committee/mod.rs @@ -36,7 +36,6 @@ impl ElectionSecretKey { #[derive(Debug, Clone, PartialEq, Eq)] pub struct ElectionPublicKey(pub(crate) GroupElement); -#[cfg(any(test, feature = "proptest-arbitrary"))] #[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::prelude::{any, Arbitrary, BoxedStrategy, Strategy}; diff --git a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs index f1fe07d1faa..f1670846136 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/mod.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/mod.rs @@ -141,7 +141,6 @@ pub fn decrypt_vote(vote: &EncryptedVote, secret_key: &ElectionSecretKey) -> any bail!("Invalid encrypted vote, not a valid unit vector.") } -#[cfg(any(test, feature = "proptest-arbitrary"))] #[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::{ diff --git a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs index d580dcf8532..a244dcc870e 100644 --- a/rust/catalyst-voting/src/vote_protocol/voter/proof.rs +++ b/rust/catalyst-voting/src/vote_protocol/voter/proof.rs @@ -107,7 +107,6 @@ pub fn verify_voter_proof( verify_unit_vector_proof(&proof.0, encrypted_vote.0, &public_key.0, &commitment.0) } -#[cfg(any(test, feature = "proptest-arbitrary"))] #[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::prelude::{any_with, Arbitrary, BoxedStrategy, Strategy}; diff --git a/rust/jormungandr-vote-tx/Cargo.toml b/rust/jormungandr-vote-tx/Cargo.toml index ebb98f2095a..42a2088acba 100644 --- a/rust/jormungandr-vote-tx/Cargo.toml +++ b/rust/jormungandr-vote-tx/Cargo.toml @@ -11,19 +11,11 @@ license.workspace = true workspace = true [dependencies] -catalyst-voting = { version = "0.0.1", path = "../catalyst-voting" } +catalyst-voting = { version = "0.0.1", path = "../catalyst-voting" } anyhow = "1.0.89" -proptest = { version = "1.5.0", optional = true } +proptest = { version = "1.5.0" } [dev-dependencies] -catalyst-voting = { version = "0.0.1", path = "../catalyst-voting", features = ["proptest-arbitrary"] } - -proptest = { version = "1.5.0" } # Potentially it could be replaced with using `proptest::property_test` attribute macro, # after this PR will be merged https://github.com/proptest-rs/proptest/pull/523 test-strategy = "0.4.0" - -[features] -# Feature which makes publicly available -# `proptest::prelude::Arbitrary` trait implementation for some types -proptest-arbitrary = ["catalyst-voting/proptest-arbitrary", "dep:proptest"] diff --git a/rust/jormungandr-vote-tx/src/lib.rs b/rust/jormungandr-vote-tx/src/lib.rs index fa06e477180..c8859180aa9 100644 --- a/rust/jormungandr-vote-tx/src/lib.rs +++ b/rust/jormungandr-vote-tx/src/lib.rs @@ -295,7 +295,6 @@ impl VotePayload { } } -#[cfg(any(test, feature = "proptest-arbitrary"))] #[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use catalyst_voting::crypto::ed25519::PrivateKey;