Hack The Vote 2024
Solution of one Crypto chall
ZeroVote
You can found the source code here. In short we have two main options
- Cast a vote for Alice, Bob, or Carol.
- Vote with a valid vote
If Carol
win we get flag.
But the problem is when we do the voting Alice and Bob always have 50 votes but if we only can get one vote for carol from 1
and therefore Carol can’t win.
1
2
3
4
5
6
7
8
9
10
11
fn init_ballots() -> Vec<Ballot> {
let mut rng = OsRng;
let mut ballots = Vec::new();
for _ in 0..50 {
ballots.push(Ballot::new(Scalar::ONE, Scalar::ZERO, Scalar::ZERO, &mut rng));
}
for _ in 0..50 {
ballots.push(Ballot::new(Scalar::ZERO, Scalar::ONE, Scalar::ZERO, &mut rng));
}
ballots
}
Let talk about the PedersenCommitment
in the code below
1
2
3
4
5
6
7
8
9
10
impl PedersenCommitment {
fn commit(x: Scalar, r: Scalar) -> PedersenCommitment {
PedersenCommitment {
point: RistrettoPoint::mul_base(&x) + *BLINDING_FACTOR * &r,
}
}
}
...
let ag = self.total_a.count.point - *BLINDING_FACTOR * self.total_a.x;
...
\(P = RS*x + BF * r, x\in(0,1)\)
But in the end they eliminate the random $r$ so we don’t need to consider it.
Take a closer look at decrypt
when checking their votes let (a, b, c) = aggregate.decrypt();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
fn decrypt(&self) -> (u64, u64, u64) {
let ag = self.total_a.count.point - *BLINDING_FACTOR * self.total_a.x;
let bg = self.total_b.count.point - *BLINDING_FACTOR * self.total_b.x;
let cg = self.total_c.count.point - *BLINDING_FACTOR * self.total_c.x;
let (mut a, mut b, mut c) = (0, 0, 0);
let mut tmp = RistrettoPoint::identity();
for i in 0..self.count {
if ag == tmp {
a = i;
}
if bg == tmp {
b = i;
}
if cg == tmp {
c = i;
}
tmp += RistrettoPoint::mul_base(&Scalar::ONE);
}
(a, b, c)
}
impl ops::AddAssign<&Ballot> for VoteAggregate {
fn add_assign(&mut self, ballot: &Ballot) {
if ballot.p.check().is_ok() {
self.total_a += &ballot.a;
self.total_b += &ballot.b;
self.total_c += &ballot.c;
self.count += 1;
}
}
}
After verifying each valid vote, it adds the votes of the three people and calculates the total.point
Therefore, we need $cg = RS*51$ to obtain the flag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
impl Proof {
fn generate(ctxt: VoteCiphertext, rng: &mut impl CryptoRngCore) -> Proof {
let r = Scalar::random(rng);
let xh = -RistrettoPoint::mul_base(&Scalar::ONE) + ctxt.count.point;
let rh = *BLINDING_FACTOR * &r;
let c = Scalar::from_hash::<Sha512>(Sha512::new().chain(xh.to_bytes()).chain(rh.to_bytes()));
let xcr = ctxt.x * c + r;
Proof { xh, rh, xcr }
}
fn check(&self) -> Result<(), ()> {
let c = Scalar::from_hash::<Sha512>(Sha512::new().chain(self.xh.to_bytes()).chain(self.rh.to_bytes()));
let lhs = *BLINDING_FACTOR * &self.xcr;
let rhs = self.rh + self.xh * &c;
if lhs == rhs {
Ok(())
} else {
Err(())
}
}
}
We can forge any ballot with parameters $x$, $r$ because
lhs = BF*(c+1) == BF + BF*c = rhs
So, I will create a ballot with 51 votes for Carol and 0 votes for both Alice and Bob, using a forged proof to obtain the flag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
impl Ballot {
fn new(a: Scalar, b: Scalar, c: Scalar, rng: &mut impl CryptoRngCore) -> Ballot {
let a = VoteCiphertext::new(a, rng);
let b = VoteCiphertext::new(b, rng);
let c = VoteCiphertext::new(c, rng);
let p = Proof::generate(a.clone() + &b + &c, rng);
Ballot { a, b, c, p }
}
pub fn set_p(&mut self, new_p: Proof) {
self.p = new_p;
}
}
fn main() -> io::Result<()> {
let mut rng = OsRng;
let rh = *BLINDING_FACTOR;
let xh = *BLINDING_FACTOR;
let c = Scalar::from_hash::<Sha512>(Sha512::new().chain(xh.to_bytes()).chain(rh.to_bytes()));
let xcr = c + Scalar::ONE;
let lhs = *BLINDING_FACTOR * &xcr;
let rhs = rh + xh * &c;
let mut ballot = Ballot::new(Scalar::ZERO, Scalar::ZERO, Scalar::from(51u64), &mut OsRng);
let newp = Proof {xh, rh, xcr};
ballot.set_p(newp);
println!("Here's your ballot: {}", serde_json::to_string(&ballot).unwrap());
println!("{:?}", ballot.p.check());
Ok(())
}