Skip to content

Commit 8c8de61

Browse files
committed
fix!(statetransition): fix many bugs
* fix!(statetransition): bind ProcessProofs Keys to params.StateKeys, and verify IsValidCensusOrigin * fix!(statetransition): ResultsAdd and ResultsSub transitions must be UPDATE * fix!(statetransition): verify circuit.VotesProofs.VoteIDs invariants * fix!(statetransition): encode VotersCount in blobs this fixes a vulnerability that allowed arbitrary dummy-slot VoteID, Address, and ReencryptedBallot witness values to be committed into the blob while not being tied to any verified leaf update * fix!(statetransition): VerifyMerkleTransitionKeys * refactor(statetransition): unify range loop style
1 parent d06d8b0 commit 8c8de61

11 files changed

Lines changed: 356 additions & 154 deletions

File tree

census/gnark.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,12 @@ func IsMerkleTreeCensusOrigin(api frontend.API, origin frontend.Variable) fronte
2828
func IsCSPCensusOrigin(api frontend.API, origin frontend.Variable) frontend.Variable {
2929
return api.IsZero(api.Sub(origin, uint8(types.CensusOriginCSPEdDSABabyJubJubV1)))
3030
}
31+
32+
// IsValidCensusOrigin returns a frontend.Variable that is 1 if the provided
33+
// origin corresponds to any supported census origin, 0 otherwise.
34+
func IsValidCensusOrigin(api frontend.API, origin frontend.Variable) frontend.Variable {
35+
return api.Or(
36+
IsMerkleTreeCensusOrigin(api, origin),
37+
IsCSPCensusOrigin(api, origin),
38+
)
39+
}

circuits/statetransition/inputs.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"math/big"
66

77
"github.com/vocdoni/davinci-node/circuits/merkleproof"
8+
"github.com/vocdoni/davinci-node/spec/params"
89
"github.com/vocdoni/davinci-node/state"
910
"github.com/vocdoni/davinci-node/types"
1011
)
@@ -83,7 +84,7 @@ func GenerateAssignment(
8384
return nil, nil, fmt.Errorf("could not get EncryptionKey proof: %w", err)
8485
}
8586
// add Ballots and VoteIDs proofs
86-
for i := range assignment.VotesProofs.Ballot {
87+
for i := range params.VotesPerBatch {
8788
// ballots
8889
assignment.VotesProofs.Ballot[i], err = merkleproof.MerkleTransitionFromArboTransition(o.VotesProofs().Ballot[i])
8990
if err != nil {

circuits/statetransition/statetransition.go

Lines changed: 66 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ type Results struct {
6767
}
6868

6969
// ProcessProofs struct contains the Merkle proofs for the process for the ID
70-
// CensusRoot, BallotMode and EncryptionKey.
70+
// CensusOrigin, BallotMode and EncryptionKey.
7171
type ProcessProofs struct {
7272
ID merkleproof.MerkleProof
7373
CensusOrigin merkleproof.MerkleProof
@@ -84,9 +84,8 @@ type CensusProofs struct {
8484
}
8585

8686
// VotesProofs struct contains the Merkle transition proofs for the ballots and
87-
// commitments.
87+
// voteIDs.
8888
type VotesProofs struct {
89-
// Key is Address, LeafHash is smt.Hash1(encoded(Ballot.Serialize()))
9089
Ballot [params.VotesPerBatch]merkleproof.MerkleTransition
9190
VoteIDs [params.VotesPerBatch]merkleproof.MerkleTransition
9291
}
@@ -114,13 +113,16 @@ func (circuit StateTransitionCircuit) Define(api frontend.API) error {
114113
// recursive proof
115114
circuit.VerifyAggregatorProof(api, isRealVote)
116115
// current state
117-
circuit.VerifyMerkleProofs(api, HashFn)
116+
circuit.VerifyProcessProofKeys(api)
117+
circuit.VerifyProcessProofs(api, HashFn)
118118
// state transition
119+
circuit.VerifyMerkleTransitionKeys(api)
119120
circuit.VerifyMerkleTransitions(api, isRealVote)
120121
circuit.VerifyRootTransition(api, HashFn)
121122
// leaf hashes
122123
circuit.VerifyLeafHashes(api, HashFn)
123124
// censuses
125+
circuit.VerifyIsValidCensusOrigin(api)
124126
circuit.VerifyMerkleCensusProofs(api, isRealVote)
125127
circuit.VerifyCSPCensusProofs(api, isRealVote)
126128
// votes reencryption and ballots
@@ -249,33 +251,49 @@ func (circuit StateTransitionCircuit) VerifyAggregatorProof(api frontend.API, is
249251
// To reencrypt the votes, it adds the encrypted zero ballot to the original
250252
// ballot. The encrypted zero uses the reencryptionK as the randomness.
251253
func (circuit StateTransitionCircuit) VerifyReencryptedVotes(api frontend.API, isRealVote []frontend.Variable) {
252-
lastK := frontend.Variable(circuit.ReencryptionK)
253-
for i, v := range circuit.Votes {
254-
var err error
255-
var reencryptedBallot *circuits.Ballot
256-
reencryptedBallot, lastK, err = v.Ballot.Reencrypt(api, circuit.Process.EncryptionKey, lastK)
254+
lastK := circuit.ReencryptionK
255+
for i := range params.VotesPerBatch {
256+
v := circuit.Votes[i]
257+
reencryptedBallot, k, err := v.Ballot.Reencrypt(api, circuit.Process.EncryptionKey, lastK)
257258
if err != nil {
258259
circuits.FrontendError(api, "failed to reencrypt ballot: ", err)
259260
return
260261
}
261262
circuits.AssertTrueIf(api, isRealVote[i], v.ReencryptedBallot.IsEqual(api, reencryptedBallot))
263+
lastK = k
262264
}
263265
}
264266

265-
// VerifyMerkleProofs verifies that the ProcessID, CensusRoot, BallotMode
267+
// VerifyProcessProofKeys asserts that the process proofs are bound to the
268+
// canonical state keys for each process parameter.
269+
func (circuit StateTransitionCircuit) VerifyProcessProofKeys(api frontend.API) {
270+
api.AssertIsEqual(circuit.ProcessProofs.ID.Key, params.StateKeyProcessID)
271+
api.AssertIsEqual(circuit.ProcessProofs.CensusOrigin.Key, params.StateKeyCensusOrigin)
272+
api.AssertIsEqual(circuit.ProcessProofs.BallotMode.Key, params.StateKeyBallotMode)
273+
api.AssertIsEqual(circuit.ProcessProofs.EncryptionKey.Key, params.StateKeyEncryptionKey)
274+
}
275+
276+
// VerifyProcessProofs verifies that the ProcessID, CensusOrigin, BallotMode
266277
// and EncryptionKey belong to the RootHashBefore. It uses the MerkleProof
267278
// structure to verify the proofs. The proofs are verified using the Verify
268279
// function of the MerkleProof structure.
269-
func (circuit StateTransitionCircuit) VerifyMerkleProofs(api frontend.API, hFn utils.Hasher) {
280+
func (circuit StateTransitionCircuit) VerifyProcessProofs(api frontend.API, hFn utils.Hasher) {
270281
circuit.ProcessProofs.ID.Verify(api, hFn, circuit.RootHashBefore)
271282
circuit.ProcessProofs.CensusOrigin.Verify(api, hFn, circuit.RootHashBefore)
272283
circuit.ProcessProofs.BallotMode.Verify(api, hFn, circuit.RootHashBefore)
273284
circuit.ProcessProofs.EncryptionKey.Verify(api, hFn, circuit.RootHashBefore)
274285
}
275286

287+
// VerifyIsValidCensusOrigin asserts that the census origin is one of the
288+
// variants currently supported by the circuit.
289+
func (circuit StateTransitionCircuit) VerifyIsValidCensusOrigin(api frontend.API) {
290+
api.AssertIsEqual(census.IsValidCensusOrigin(api, circuit.Process.CensusOrigin), 1)
291+
}
292+
276293
// VerifyMerkleTransitions enforces that each MerkleTransition is of the expected type:
277294
// - Ballot transitions must be INSERT or UPDATE
278295
// - VoteID transitions must be INSERT
296+
// - ResultsAdd and ResultsSub transitions must be UPDATE
279297
// - all dummy slots must be NOOP
280298
func (circuit StateTransitionCircuit) VerifyMerkleTransitions(api frontend.API, isRealVote []frontend.Variable) {
281299
for i := range params.VotesPerBatch {
@@ -287,6 +305,20 @@ func (circuit StateTransitionCircuit) VerifyMerkleTransitions(api frontend.API,
287305
circuits.AssertTrueIf(api, isDummy, circuit.VotesProofs.Ballot[i].IsNoop(api))
288306
circuits.AssertTrueIf(api, isDummy, circuit.VotesProofs.VoteIDs[i].IsNoop(api))
289307
}
308+
api.AssertIsEqual(circuit.ResultsProofs.ResultsAdd.IsUpdate(api), 1)
309+
api.AssertIsEqual(circuit.ResultsProofs.ResultsSub.IsUpdate(api), 1)
310+
}
311+
312+
// VerifyMerkleTransitionKeys asserts that the merkle transition keys are bound to the
313+
// canonical state keys or namespaces.
314+
func (circuit StateTransitionCircuit) VerifyMerkleTransitionKeys(api frontend.API) {
315+
circuit.ResultsProofs.ResultsAdd.VerifyNewKey(api, params.StateKeyResultsAdd)
316+
circuit.ResultsProofs.ResultsSub.VerifyNewKey(api, params.StateKeyResultsSub)
317+
// Votes
318+
for i := range params.VotesPerBatch {
319+
circuit.VotesProofs.VoteIDs[i].VerifyNewKey(api, circuit.Votes[i].VoteID)
320+
circuit.VotesProofs.Ballot[i].VerifyNewKey(api, circuit.Votes[i].BallotIndex)
321+
}
290322
}
291323

292324
// VerifyRootTransition verifies that the chain of tree transitions is valid.
@@ -326,33 +358,36 @@ func (circuit StateTransitionCircuit) VerifyLeafHashes(api frontend.API, hFn uti
326358
return
327359
}
328360
// Votes
329-
for i, v := range circuit.Votes {
330-
// Address
331-
circuit.VotesProofs.Ballot[i].VerifyNewKey(api, v.BallotIndex)
361+
for i := range params.VotesPerBatch {
332362
// Ballot
333-
if err := circuit.VotesProofs.Ballot[i].VerifyNewLeafHash(api, hFn, v.ReencryptedBallot.SerializeVars()...); err != nil {
363+
if err := circuit.VotesProofs.Ballot[i].VerifyNewLeafHash(api, hFn, circuit.Votes[i].ReencryptedBallot.SerializeVars()...); err != nil {
334364
circuits.FrontendError(api, "failed to verify ballot vote proof leaf hash: ", err)
335365
return
336366
}
337367
// OverwrittenBallot
338-
if err := circuit.VotesProofs.Ballot[i].VerifyOverwrittenBallot(api, hFn, v.OverwrittenBallot.SerializeVars()...); err != nil {
368+
if err := circuit.VotesProofs.Ballot[i].VerifyOverwrittenBallot(api, hFn, circuit.Votes[i].OverwrittenBallot.SerializeVars()...); err != nil {
339369
circuits.FrontendError(api, "failed to verify ballot vote proof leaf hash: ", err)
340370
return
341371
}
372+
// VoteID
373+
if err := circuit.VotesProofs.VoteIDs[i].VerifyNewLeafHash(api, hFn, params.VoteIDLeafValue); err != nil {
374+
circuits.FrontendError(api, "failed to verify voteID vote proof leaf hash: ", err)
375+
return
376+
}
342377
}
343378
// Results
344379
if err := circuit.ResultsProofs.ResultsAdd.VerifyOldLeafHash(api, hFn, circuit.Results.OldResultsAdd.SerializeVars()...); err != nil {
345380
circuits.FrontendError(api, "failed to verify add results proof old leaf hash: ", err)
346381
return
347382
}
348-
if err := circuit.ResultsProofs.ResultsSub.VerifyOldLeafHash(api, hFn, circuit.Results.OldResultsSub.SerializeVars()...); err != nil {
349-
circuits.FrontendError(api, "failed to verify sub results proof old leaf hash: ", err)
350-
return
351-
}
352383
if err := circuit.ResultsProofs.ResultsAdd.VerifyNewLeafHash(api, hFn, circuit.Results.NewResultsAdd.SerializeVars()...); err != nil {
353384
circuits.FrontendError(api, "failed to verify add results proof new leaf hash: ", err)
354385
return
355386
}
387+
if err := circuit.ResultsProofs.ResultsSub.VerifyOldLeafHash(api, hFn, circuit.Results.OldResultsSub.SerializeVars()...); err != nil {
388+
circuits.FrontendError(api, "failed to verify sub results proof old leaf hash: ", err)
389+
return
390+
}
356391
if err := circuit.ResultsProofs.ResultsSub.VerifyNewLeafHash(api, hFn, circuit.Results.NewResultsSub.SerializeVars()...); err != nil {
357392
circuits.FrontendError(api, "failed to verify sub results proof new leaf hash: ", err)
358393
return
@@ -365,9 +400,8 @@ func (circuit StateTransitionCircuit) VerifyBlobs(api frontend.API) {
365400
// Build blob and verify evaluation
366401
//
367402
// The blob is built as follows:
368-
// - First, we add the new results (addition and subtraction) - always present
369-
// - Then, we add the votes sequentially (no padding)
370-
// - Finally, we add a sentinel (voteID = 0x0) to mark end of votes
403+
// - First, we add the new results (addition and subtraction) and VotersCount
404+
// - Finally, we add exactly VotersCount votes sequentially
371405
// Each ballot coordinate is represented as a field element (32 bytes).
372406
// Each field element is represented as a big-endian byte array.
373407
// The blob is a fixed-size array (FieldElementsPerBlob * BytesPerFieldElement).
@@ -380,30 +414,23 @@ func (circuit StateTransitionCircuit) VerifyBlobs(api frontend.API) {
380414
blobIndex++
381415
}
382416
}
383-
// Always include results (no sentinel applies to them)
417+
// Always include results.
384418
appendBallotMasked(circuit.Results.NewResultsAdd, 1)
385419
appendBallotMasked(circuit.Results.NewResultsSub, 1)
386-
// Votes section with sentinel handling.
387-
// keep==1 means "we haven't seen sentinel yet". Once we see voteID==0,
388-
// keep becomes 0 and stays 0, zeroing out everything afterwards.
389-
keep := frontend.Variable(1)
420+
blob[blobIndex] = circuit.VotersCount
421+
blobIndex++
422+
isRealVote := circuit.VoteMask(api)
390423
for i := range params.VotesPerBatch {
391-
voteID := circuit.Votes[i].VoteID
392-
isZero := api.IsZero(voteID) // 1 if voteID==0 else 0
393-
notZero := api.Sub(1, isZero) // 1 if voteID!=0 else 0
394-
// Only write this vote if keep==1 AND voteID!=0
395-
writeMask := api.Mul(keep, notZero)
424+
writeMask := isRealVote[i]
396425
// VoteID, Address and BallotIndex
397-
blob[blobIndex] = api.Mul(writeMask, voteID)
426+
blob[blobIndex] = api.Mul(writeMask, circuit.Votes[i].VoteID)
398427
blobIndex++
399428
blob[blobIndex] = api.Mul(writeMask, circuit.Votes[i].Address)
400429
blobIndex++
401430
blob[blobIndex] = api.Mul(writeMask, circuit.Votes[i].BallotIndex)
402431
blobIndex++
403432
// Reencrypted ballot (masked)
404433
appendBallotMasked(circuit.Votes[i].ReencryptedBallot, writeMask)
405-
// Update keep for next iterations: once we saw 0, keep→0 forever
406-
keep = api.Mul(keep, notZero)
407434
}
408435
// Fill the rest of the blob with zeros
409436
for i := blobIndex; i < len(blob); i++ {
@@ -431,9 +458,9 @@ func (circuit StateTransitionCircuit) VerifyBallots(api frontend.API) {
431458
sumOfAllBallots, sumOfOverwrittenBallots, zero := circuits.NewBallot(), circuits.NewBallot(), circuits.NewBallot()
432459
var votersCount, overwrittenVotesCount frontend.Variable = 0, 0
433460

434-
for i, b := range circuit.VotesProofs.Ballot {
435-
isInsertOrUpdate := b.IsInsertOrUpdate(api)
436-
isUpdate := b.IsUpdate(api)
461+
for i := range params.VotesPerBatch {
462+
isInsertOrUpdate := circuit.VotesProofs.Ballot[i].IsInsertOrUpdate(api)
463+
isUpdate := circuit.VotesProofs.Ballot[i].IsUpdate(api)
437464

438465
ballot := circuits.NewBallot().Select(api, isInsertOrUpdate, &circuit.Votes[i].ReencryptedBallot, zero)
439466
sumOfAllBallots.Add(api, sumOfAllBallots, ballot)

0 commit comments

Comments
 (0)