Skip to content

Commit 2f9277f

Browse files
Mr-Leshiyminikin
andauthored
feat(rust/catalyst-voting): Vote protocol benchmarks (#65)
* add vote protocol benchmarks * add missing benchmarks * refactor * cleanup * wip --------- Co-authored-by: Oleksandr Prokhorenko <[email protected]>
1 parent 5acc2b7 commit 2f9277f

File tree

9 files changed

+282
-32
lines changed

9 files changed

+282
-32
lines changed

rust/.cargo/config.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ lint-vscode = "clippy --message-format=json-diagnostic-rendered-ansi --all-targe
7777

7878
docs = "doc --release --no-deps --document-private-items --bins --lib --examples"
7979
# nightly docs build broken... when they are'nt we can enable these docs... --unit-graph --timings=html,json -Z unstable-options"
80-
testunit = "nextest run --release --bins --lib --tests --benches --no-fail-fast -P ci"
81-
testcov = "llvm-cov nextest --release --bins --lib --tests --benches --no-fail-fast -P ci"
80+
testunit = "nextest run --release --bins --lib --tests --no-fail-fast -P ci"
81+
testcov = "llvm-cov nextest --release --bins --lib --tests --no-fail-fast -P ci"
8282
testdocs = "test --doc --release"
8383

8484
# Rust formatting, MUST be run with +nightly

rust/Earthfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
VERSION 0.8
22

3-
IMPORT github.com/input-output-hk/catalyst-ci/earthly/rust:v3.2.15 AS rust-ci
3+
IMPORT github.com/input-output-hk/catalyst-ci/earthly/rust:v3.2.19 AS rust-ci
44

55
COPY_SRC:
66
FUNCTION

rust/catalyst-voting/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ license.workspace = true
1212
[lints]
1313
workspace = true
1414

15+
[[bench]]
16+
name = "vote_protocol"
17+
harness = false
18+
1519
[dependencies]
1620
anyhow = "1.0.89"
1721
rand_core = { version = "0.6.4", features = ["getrandom"] }
@@ -21,7 +25,8 @@ ed25519-dalek = { version = "2.1.1", features = ["rand_core"] }
2125
blake2b_simd = "1.0.2"
2226

2327
[dev-dependencies]
24-
proptest = {version = "1.5.0" }
28+
criterion = "0.5.1"
29+
proptest = { version = "1.5.0" }
2530
# Potentially it could be replaced with using `proptest::property_test` attribute macro,
2631
# after this PR will be merged https://github.com/proptest-rs/proptest/pull/523
2732
test-strategy = "0.4.0"
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
//! `catalyst_voting::vote_protocol` benchmark
2+
//!
3+
//! To run these benchmarks use
4+
//! ```shell
5+
//! SAMPLE_SIZE=<sample size> VOTERS_NUMBER=<voters number> cargo bench -p catalyst-voting vote_protocol
6+
//! ```
7+
#![allow(
8+
missing_docs,
9+
clippy::missing_docs_in_private_items,
10+
clippy::unwrap_used,
11+
clippy::similar_names
12+
)]
13+
14+
use catalyst_voting::{
15+
crypto::default_rng,
16+
vote_protocol::{
17+
committee::{ElectionPublicKey, ElectionSecretKey},
18+
tally::{
19+
decrypt_tally,
20+
proof::{generate_tally_proof, verify_tally_proof},
21+
tally, DecryptionTallySetup,
22+
},
23+
voter::{
24+
encrypt_vote,
25+
proof::{generate_voter_proof, verify_voter_proof, VoterProofCommitment},
26+
Vote,
27+
},
28+
},
29+
};
30+
use criterion::{criterion_group, criterion_main, Criterion};
31+
use proptest::{
32+
prelude::{any_with, Strategy},
33+
sample::size_range,
34+
strategy::ValueTree,
35+
test_runner::TestRunner,
36+
};
37+
use test_strategy::Arbitrary;
38+
39+
const VOTERS_NUMBER_ENV: &str = "VOTERS_NUMBER";
40+
const SAMPLE_SIZE_ENV: &str = "SAMPLE_SIZE";
41+
const DEFAULT_SAMPLE_SIZE: usize = 10;
42+
const DEFAULT_VOTERS_NUMBER: usize = 1;
43+
44+
const VOTING_OPTIONS: usize = 3;
45+
46+
#[derive(Arbitrary, Debug)]
47+
struct Voter {
48+
voting_power: u32,
49+
#[strategy(0..VOTING_OPTIONS)]
50+
choice: usize,
51+
}
52+
53+
struct Choices(Vec<usize>);
54+
struct VotingPowers(Vec<u64>);
55+
56+
fn rand_generate_vote_data(
57+
voters_number: usize,
58+
) -> (
59+
Choices,
60+
VotingPowers,
61+
ElectionSecretKey,
62+
ElectionPublicKey,
63+
VoterProofCommitment,
64+
) {
65+
let mut runner = TestRunner::default();
66+
67+
let (choices, voting_powers) = any_with::<Vec<Voter>>((size_range(voters_number), ()))
68+
.prop_map(|voter| {
69+
(
70+
voter.iter().map(|v| v.choice).collect(),
71+
voter.iter().map(|v| v.voting_power.into()).collect(),
72+
)
73+
})
74+
.new_tree(&mut runner)
75+
.unwrap()
76+
.current();
77+
78+
let election_secret_key = ElectionSecretKey::random_with_default_rng();
79+
let voter_proof_commitment = VoterProofCommitment::random_with_default_rng();
80+
let election_public_key = election_secret_key.public_key();
81+
82+
(
83+
Choices(choices),
84+
VotingPowers(voting_powers),
85+
election_secret_key,
86+
election_public_key,
87+
voter_proof_commitment,
88+
)
89+
}
90+
91+
#[allow(clippy::too_many_lines)]
92+
fn vote_protocol_benches(c: &mut Criterion) {
93+
let sample_size = std::env::var(SAMPLE_SIZE_ENV)
94+
.map(|s| s.parse().unwrap())
95+
.unwrap_or(DEFAULT_SAMPLE_SIZE);
96+
let voters_number = std::env::var(VOTERS_NUMBER_ENV)
97+
.map(|s| s.parse().unwrap())
98+
.unwrap_or(DEFAULT_VOTERS_NUMBER);
99+
100+
let mut group = c.benchmark_group("vote protocol benchmark");
101+
group.sample_size(sample_size);
102+
103+
let (choices, voting_powers, election_secret_key, election_public_key, voter_proof_commitment) =
104+
rand_generate_vote_data(voters_number);
105+
106+
let votes: Vec<_> = choices
107+
.0
108+
.iter()
109+
.map(|choice| Vote::new(*choice, VOTING_OPTIONS).unwrap())
110+
.collect();
111+
let mut rng = default_rng();
112+
113+
let mut encrypted_votes = Vec::new();
114+
let mut randomness = Vec::new();
115+
group.bench_function("vote encryption", |b| {
116+
b.iter(|| {
117+
(encrypted_votes, randomness) = votes
118+
.iter()
119+
.map(|vote| encrypt_vote(vote, &election_public_key, &mut rng))
120+
.unzip();
121+
});
122+
});
123+
124+
let mut voter_proofs = Vec::new();
125+
group.bench_function("voter proof generation", |b| {
126+
b.iter(|| {
127+
voter_proofs = votes
128+
.iter()
129+
.zip(encrypted_votes.iter())
130+
.zip(randomness.iter())
131+
.map(|((v, enc_v), r)| {
132+
generate_voter_proof(
133+
v,
134+
enc_v.clone(),
135+
r.clone(),
136+
&election_public_key,
137+
&voter_proof_commitment,
138+
&mut rng,
139+
)
140+
.unwrap()
141+
})
142+
.collect();
143+
});
144+
});
145+
146+
group.bench_function("voter proof verification", |b| {
147+
b.iter(|| {
148+
let is_ok = voter_proofs
149+
.iter()
150+
.zip(encrypted_votes.iter())
151+
.all(|(p, enc_v)| {
152+
verify_voter_proof(
153+
enc_v.clone(),
154+
&election_public_key,
155+
&voter_proof_commitment,
156+
p,
157+
)
158+
});
159+
assert!(is_ok);
160+
});
161+
});
162+
163+
let mut encrypted_tallies = Vec::new();
164+
group.bench_function("tally", |b| {
165+
b.iter(|| {
166+
encrypted_tallies = (0..VOTING_OPTIONS)
167+
.map(|voting_option| {
168+
tally(voting_option, &encrypted_votes, &voting_powers.0).unwrap()
169+
})
170+
.collect();
171+
});
172+
});
173+
174+
let total_voting_power = voting_powers.0.iter().sum();
175+
let mut decryption_tally_setup = None;
176+
group.bench_function("decryption tally setup initialization", |b| {
177+
b.iter(|| {
178+
decryption_tally_setup = Some(DecryptionTallySetup::new(total_voting_power).unwrap());
179+
});
180+
});
181+
let decryption_tally_setup = decryption_tally_setup.unwrap();
182+
183+
let mut decrypted_tallies = Vec::new();
184+
group.bench_function("decrypt tally", |b| {
185+
b.iter(|| {
186+
decrypted_tallies = encrypted_tallies
187+
.iter()
188+
.map(|t| decrypt_tally(t, &election_secret_key, &decryption_tally_setup).unwrap())
189+
.collect();
190+
});
191+
});
192+
193+
let mut tally_proofs = Vec::new();
194+
group.bench_function("tally proof generation", |b| {
195+
b.iter(|| {
196+
tally_proofs = encrypted_tallies
197+
.iter()
198+
.map(|t| generate_tally_proof(t, &election_secret_key, &mut rng))
199+
.collect();
200+
});
201+
});
202+
203+
group.bench_function("tally proof verification", |b| {
204+
b.iter(|| {
205+
let is_ok = tally_proofs
206+
.iter()
207+
.zip(encrypted_tallies.iter())
208+
.zip(decrypted_tallies.iter())
209+
.all(|((p, enc_t), t)| verify_tally_proof(enc_t, *t, &election_public_key, p));
210+
assert!(is_ok);
211+
});
212+
});
213+
214+
group.finish();
215+
}
216+
217+
criterion_group!(benches, vote_protocol_benches);
218+
219+
criterion_main!(benches);

rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,34 @@ impl UnitVectorProof {
2727
let ann = (0..len)
2828
.map(|i| {
2929
let bytes = read_array(reader)?;
30-
Announcement::from_bytes(&bytes)
31-
.map_err(|e| anyhow!("Cannot decode announcement at {i}, error: {e}."))
30+
Announcement::from_bytes(&bytes).map_err(|e| {
31+
anyhow!(
32+
"Cannot decode announcement at {i}, \
33+
error: {e}."
34+
)
35+
})
3236
})
3337
.collect::<anyhow::Result<_>>()?;
3438
let dl = (0..len)
3539
.map(|i| {
3640
let bytes = read_array(reader)?;
37-
Ciphertext::from_bytes(&bytes)
38-
.map_err(|e| anyhow!("Cannot decode ciphertext at {i}, error: {e}."))
41+
Ciphertext::from_bytes(&bytes).map_err(|e| {
42+
anyhow!(
43+
"Cannot decode ciphertext at {i}, \
44+
error: {e}."
45+
)
46+
})
3947
})
4048
.collect::<anyhow::Result<_>>()?;
4149
let rr = (0..len)
4250
.map(|i| {
4351
let bytes = read_array(reader)?;
44-
ResponseRandomness::from_bytes(&bytes)
45-
.map_err(|e| anyhow!("Cannot decode response randomness at {i}, error: {e}."))
52+
ResponseRandomness::from_bytes(&bytes).map_err(|e| {
53+
anyhow!(
54+
"Cannot decode response randomness at {i}, \
55+
error: {e}."
56+
)
57+
})
4658
})
4759
.collect::<anyhow::Result<_>>()?;
4860

rust/catalyst-voting/src/txs/v1/decoding.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,16 @@ impl Tx {
112112
let padding_tag = read_be_u8(reader).map_err(|_| anyhow!("Missing padding tag field."))?;
113113
ensure!(
114114
padding_tag == PADDING_TAG,
115-
"Invalid padding tag field value, must be equals to {PADDING_TAG}, provided: {padding_tag}.",
115+
"Invalid padding tag field value, must be equals to {PADDING_TAG}, \
116+
provided: {padding_tag}.",
116117
);
117118

118119
let fragment_tag =
119120
read_be_u8(reader).map_err(|_| anyhow!("Missing fragment tag field."))?;
120121
ensure!(
121122
fragment_tag == FRAGMENT_TAG,
122-
"Invalid fragment tag field value, must be equals to {FRAGMENT_TAG}, provided: {fragment_tag}.",
123+
"Invalid fragment tag field value, must be equals to {FRAGMENT_TAG}, \
124+
provided: {fragment_tag}.",
123125
);
124126

125127
let vote_plan_id =
@@ -148,7 +150,8 @@ impl Tx {
148150
},
149151
tag => {
150152
bail!(
151-
"Invalid vote tag value, must be equals to {PUBLIC_VOTE_TAG} or {PRIVATE_VOTE_TAG}, provided: {tag}"
153+
"Invalid vote tag value, \
154+
must be equals to {PUBLIC_VOTE_TAG} or {PRIVATE_VOTE_TAG}, provided: {tag}"
152155
)
153156
},
154157
};
@@ -160,20 +163,23 @@ impl Tx {
160163
read_be_u8(reader).map_err(|_| anyhow!("Missing inputs amount field."))?;
161164
ensure!(
162165
inputs_amount == NUMBER_OF_INPUTS,
163-
"Invalid number of inputs, expected: {NUMBER_OF_INPUTS}, provided: {inputs_amount}",
166+
"Invalid number of inputs, expected: {NUMBER_OF_INPUTS}, \
167+
provided: {inputs_amount}",
164168
);
165169

166170
let outputs_amount =
167171
read_be_u8(reader).map_err(|_| anyhow!("Missing outputs amount field."))?;
168172
ensure!(
169173
outputs_amount == NUMBER_OF_OUTPUTS,
170-
"Invalid number of outputs, expected: {NUMBER_OF_OUTPUTS}, provided: {outputs_amount}",
174+
"Invalid number of outputs, expected: {NUMBER_OF_OUTPUTS}, \
175+
provided: {outputs_amount}",
171176
);
172177

173178
let input_tag = read_be_u8(reader).map_err(|_| anyhow!("Missing input tag field."))?;
174179
ensure!(
175180
input_tag == INPUT_TAG,
176-
"Invalid input tag, expected: {INPUT_TAG}, provided: {input_tag}",
181+
"Invalid input tag, expected: {INPUT_TAG}, \
182+
provided: {input_tag}",
177183
);
178184

179185
// skip value
@@ -187,7 +193,8 @@ impl Tx {
187193
let witness_tag = read_be_u8(reader).map_err(|_| anyhow!("Missing witness tag field."))?;
188194
ensure!(
189195
witness_tag == WITNESS_TAG,
190-
"Invalid witness tag, expected: {WITNESS_TAG}, provided: {witness_tag}",
196+
"Invalid witness tag, expected: {WITNESS_TAG}, \
197+
provided: {witness_tag}",
191198
);
192199

193200
// Skip nonce field

0 commit comments

Comments
 (0)