Skip to content

Commit 0496dd6

Browse files
authored
address vit-2568, remove panic on babysteps if max_value = 0 (#750)
* address vit-2568, remove panic on babysteps if max_value = 0 * refactor decrypt_tally to use NonZeroU64 where possible * remove decrypt strategy enum, provide len func * create TallyDecryptor struct * fix formatting * add batch_decrypt, still todo on from_bytes implementation * working batch_decrypt * working batch_decrypt * fix serialization sanity test * add batch decrypt tests * PR feedback, remove unnecessary non-zero bounds, ... * remove max_votes from `decrypt_tally`, remove redundant test * remove unnecessary unwrap()
1 parent 836427b commit 0496dd6

File tree

5 files changed

+263
-123
lines changed

5 files changed

+263
-123
lines changed

chain-impl-mockchain/benches/tally.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ fn tally_benchmark(
6363
.sample_iter(&mut rng)
6464
.take(voters_count)
6565
.collect();
66-
let total_votes = voting_powers.iter().sum();
66+
let total_votes: u64 = voting_powers.iter().sum();
6767
let token_name: TokenName = vec![0u8; TOKEN_NAME_MAX_SIZE].try_into().unwrap();
6868
let mut voters_wallets: Vec<_> = voters_aliases
6969
.iter()
@@ -233,7 +233,7 @@ fn tally_benchmark(
233233
.collect();
234234

235235
let decrypt_tally = || {
236-
let table = chain_vote::TallyOptimizationTable::generate(total_votes);
236+
let table = chain_vote::TallyOptimizationTable::generate(total_votes.try_into().unwrap());
237237

238238
vote_plan_status
239239
.proposals
@@ -252,7 +252,7 @@ fn tally_benchmark(
252252
&decrypt_shares[i],
253253
)
254254
.unwrap()
255-
.decrypt_tally(total_votes_per_proposal[i], &table)
255+
.decrypt_tally(&table)
256256
.unwrap()
257257
})
258258
.collect::<Vec<_>>()

chain-impl-mockchain/src/testing/builders/vote.rs

+21-27
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,25 @@ use crate::{
66
vote::VotePlanStatus,
77
};
88

9+
use chain_vote::tally::batch_decrypt;
910
use rand::thread_rng;
1011

1112
pub fn decrypt_tally(
1213
vote_plan_status: &VotePlanStatus,
1314
members: &CommitteeMembersManager,
1415
) -> Result<DecryptedPrivateTally, DecryptedPrivateTallyError> {
15-
let encrypted_tally = vote_plan_status
16-
.proposals
17-
.iter()
18-
.map(|proposal| {
19-
let tally_state = proposal.tally.as_ref().unwrap();
20-
let encrypted_tally = tally_state.private_encrypted().unwrap().0.clone();
21-
let max_votes = tally_state.private_total_power().unwrap();
22-
(encrypted_tally, max_votes)
23-
})
24-
.collect::<Vec<_>>();
25-
26-
let absolute_max_votes = encrypted_tally
27-
.iter()
28-
.map(|(_encrypted_tally, max_votes)| *max_votes)
29-
.max()
30-
.unwrap();
31-
let table = chain_vote::TallyOptimizationTable::generate_with_balance(absolute_max_votes, 1);
32-
3316
let members_pks: Vec<chain_vote::MemberPublicKey> = members
3417
.members()
3518
.iter()
3619
.map(|member| member.public_key())
3720
.collect();
3821

39-
let proposals = encrypted_tally
40-
.into_iter()
41-
.map(|(encrypted_tally, max_votes)| {
22+
let (shares, tallies): (Vec<_>, Vec<_>) = vote_plan_status
23+
.proposals
24+
.iter()
25+
.map(|proposal| {
26+
let tally_state = proposal.tally.as_ref().unwrap();
27+
let encrypted_tally = tally_state.private_encrypted().unwrap().0.clone();
4228
let decrypt_shares = members
4329
.members()
4430
.iter()
@@ -48,13 +34,21 @@ pub fn decrypt_tally(
4834
let validated_tally = encrypted_tally
4935
.validate_partial_decryptions(&members_pks, &decrypt_shares)
5036
.expect("Invalid shares");
51-
let tally = validated_tally.decrypt_tally(max_votes, &table).unwrap();
52-
DecryptedPrivateTallyProposal {
53-
decrypt_shares: decrypt_shares.into_boxed_slice(),
54-
tally_result: tally.votes.into_boxed_slice(),
55-
}
37+
38+
(decrypt_shares, validated_tally)
5639
})
57-
.collect::<Vec<_>>();
40+
.unzip();
41+
42+
let tallies = batch_decrypt(&tallies).unwrap();
43+
44+
let proposals = shares
45+
.into_iter()
46+
.zip(tallies.into_iter())
47+
.map(|(shares, tally)| DecryptedPrivateTallyProposal {
48+
decrypt_shares: shares.into_boxed_slice(),
49+
tally_result: tally.votes.into_boxed_slice(),
50+
})
51+
.collect();
5852

5953
DecryptedPrivateTally::new(proposals)
6054
}

chain-impl-mockchain/src/testing/e2e/vote_private.rs

+67
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,73 @@ pub fn private_vote_cast_action_transfer_to_rewards_all_shares() {
112112
.has_remaining_rewards_equals_to(&Value(1100));
113113
}
114114

115+
#[test]
116+
pub fn shouldnt_panic_when_no_initial_tokens_and_no_votes() {
117+
let mut rng = TestGen::rand();
118+
let favorable = Choice::new(1);
119+
let members = VoteTestGen::committee_members_manager(MEMBERS_NO, THRESHOLD);
120+
121+
let (mut ledger, controller) = prepare_scenario()
122+
.with_config(
123+
ConfigBuilder::new()
124+
.with_fee(LinearFee::new(1, 1, 1))
125+
.with_rewards(Value(1000)),
126+
)
127+
.with_initials(vec![wallet(ALICE)
128+
.with(1_000)
129+
.owns(STAKE_POOL)
130+
.committee_member()])
131+
.with_vote_plans(vec![vote_plan(VOTE_PLAN)
132+
.owner(ALICE)
133+
.consecutive_epoch_dates()
134+
.payload_type(PayloadType::Private)
135+
.committee_keys(members.members_keys())
136+
.with_proposal(
137+
proposal(VoteTestGen::external_proposal_id())
138+
.options(3)
139+
.action_transfer_to_rewards(100),
140+
)])
141+
.build()
142+
.unwrap();
143+
144+
let mut alice = controller.wallet(ALICE).unwrap();
145+
let vote_plan = controller.vote_plan(VOTE_PLAN).unwrap();
146+
let proposal = vote_plan.proposal(0);
147+
148+
controller
149+
.cast_vote_private(
150+
&alice,
151+
&vote_plan,
152+
&proposal.id(),
153+
favorable,
154+
&mut ledger,
155+
&mut rng,
156+
)
157+
.unwrap();
158+
alice.confirm_transaction();
159+
160+
ledger.fast_forward_to(BlockDate {
161+
epoch: 1,
162+
slot_id: 1,
163+
});
164+
165+
controller
166+
.encrypted_tally(&alice, &vote_plan, &mut ledger)
167+
.unwrap();
168+
alice.confirm_transaction();
169+
170+
let vote_plans = ledger.ledger.active_vote_plans();
171+
let vote_plan_status = vote_plans
172+
.iter()
173+
.find(|c_vote_plan| {
174+
let vote_plan: VotePlan = vote_plan.clone().into();
175+
c_vote_plan.id == vote_plan.to_id()
176+
})
177+
.unwrap();
178+
179+
decrypt_tally(vote_plan_status, &members).unwrap();
180+
}
181+
115182
#[test]
116183
#[should_panic]
117184
pub fn private_vote_plan_without_keys() {

chain-vote/src/math/babystep.rs

+14-16
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use crate::Coordinate;
55
use crate::{GroupElement, Scalar};
66
use rayon::prelude::*;
7-
use std::collections::HashMap;
7+
use std::{collections::HashMap, num::NonZeroU64};
88

99
// make steps asymmetric, in order to better use caching of baby steps.
1010
// balance of 2 means that baby steps are 2 time more than sqrt(max_votes)
@@ -25,8 +25,8 @@ pub struct BabyStepsTable {
2525
impl BabyStepsTable {
2626
/// Generate the table with asymmetrical steps,
2727
/// optimized for multiple reuse of the same table.
28-
pub fn generate(max_value: u64) -> Self {
29-
Self::generate_with_balance(max_value, DEFAULT_BALANCE)
28+
pub fn generate(max_value: NonZeroU64) -> Self {
29+
Self::generate_with_balance(max_value, DEFAULT_BALANCE.try_into().unwrap())
3030
}
3131

3232
/// Generate the table with the given balance. Balance is used to make
@@ -36,9 +36,9 @@ impl BabyStepsTable {
3636
///
3737
/// For example, a balance of 2 means that the table will precompute 2 times more
3838
/// baby steps than the standard O(sqrt(n)), 1 means symmetrical steps.
39-
pub fn generate_with_balance(max_value: u64, balance: u64) -> Self {
40-
let sqrt_step_size = (max_value as f64).sqrt().ceil() as u64;
41-
let baby_step_size = sqrt_step_size * balance;
39+
pub fn generate_with_balance(max_value: NonZeroU64, balance: NonZeroU64) -> Self {
40+
let sqrt_step_size = (u64::from(max_value) as f64).sqrt().ceil() as u64;
41+
let baby_step_size = sqrt_step_size * u64::from(balance);
4242
let mut bs = HashMap::new();
4343
let gen = GroupElement::generator();
4444
let mut e = GroupElement::zero();
@@ -57,7 +57,6 @@ impl BabyStepsTable {
5757
e = e + &gen;
5858
}
5959
assert!(!bs.is_empty());
60-
assert!(baby_step_size > 0);
6160
Self {
6261
table: bs,
6362
baby_step_size,
@@ -117,15 +116,14 @@ mod tests {
117116
Generator,
118117
};
119118

120-
#[test]
121-
#[should_panic]
122-
fn table_not_empty() {
123-
let _ = BabyStepsTable::generate_with_balance(0, 1);
119+
/// helper to convert a numeric type to a `NonZeroU64`
120+
fn nz<N: Into<i32>>(n: N) -> NonZeroU64 {
121+
(n.into() as u64).try_into().unwrap()
124122
}
125123

126124
#[test]
127125
fn quick() {
128-
let table = BabyStepsTable::generate_with_balance(25, 1);
126+
let table = BabyStepsTable::generate_with_balance(nz(25), nz(1));
129127
let p = GroupElement::generator();
130128
let votes = (0..100).collect::<Vec<_>>();
131129
let points = votes
@@ -151,14 +149,14 @@ mod tests {
151149

152150
fn table_generator_default() -> BoxGenerator<BabyStepsTable> {
153151
generator::range::<u16>(1..u16::MAX)
154-
.map(|a| BabyStepsTable::generate(a as u64))
152+
.map(|a| BabyStepsTable::generate(NonZeroU64::try_from(a as u64).unwrap()))
155153
.into_boxed()
156154
}
157155

158156
fn table_generator_with_balance() -> BoxGenerator<BabyStepsTable> {
159157
generator::range::<u16>(1..u16::MAX)
160158
.and(generator::range::<u8>(1..u8::MAX))
161-
.map(|(n, b)| BabyStepsTable::generate_with_balance(n as u64, b as u64))
159+
.map(|(n, b)| BabyStepsTable::generate_with_balance(nz(n), nz(b)))
162160
.into_boxed()
163161
}
164162

@@ -173,7 +171,7 @@ mod tests {
173171

174172
property::equal(
175173
ks,
176-
baby_step_giant_step(points.to_vec(), u16::MAX as u64, table).unwrap(),
174+
baby_step_giant_step(points.to_vec(), u16::MAX.into(), table).unwrap(),
177175
)
178176
})
179177
.test(ctx);
@@ -182,7 +180,7 @@ mod tests {
182180
let (points, ks): (Vec<_>, Vec<_>) = points.to_vec().into_iter().unzip();
183181
property::equal(
184182
ks,
185-
baby_step_giant_step(points.to_vec(), u16::MAX as u64, table).unwrap(),
183+
baby_step_giant_step(points.to_vec(), u16::MAX.into(), table).unwrap(),
186184
)
187185
})
188186
.test(ctx);

0 commit comments

Comments
 (0)