Post

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

\[\to bg = ag = RS*50\]

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(())
}
This post is licensed under CC BY 4.0 by the author.