Skip to content

Commit f34cbda

Browse files
committed
fix(results): constrain accumulators to the bjj order
Range-constrain the results accumulators to the BabyJub subgroup order before they are used as scalar inputs. This prevents wrapped field values from being interpreted as valid tally deltas under a weaker mod-field relation than the host-side arithmetic expects. Tweaked testutil NewVoteForTest to cover the edge case of a zero Result
1 parent 0471c8c commit f34cbda

3 files changed

Lines changed: 30 additions & 2 deletions

File tree

circuits/results/results.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package results
22

33
import (
44
"errors"
5+
"math/big"
56

67
"github.com/consensys/gnark/frontend"
78
"github.com/consensys/gnark/std/algebra/native/twistededwards"
89
"github.com/vocdoni/davinci-node/circuits"
910
"github.com/vocdoni/davinci-node/circuits/merkleproof"
11+
bjj "github.com/vocdoni/davinci-node/crypto/ecc/bjj_gnark"
12+
"github.com/vocdoni/davinci-node/crypto/ecc/curves"
1013
"github.com/vocdoni/davinci-node/spec/params"
1114
"github.com/vocdoni/davinci-node/state"
1215
"github.com/vocdoni/gnark-crypto-primitives/elgamal"
@@ -110,8 +113,12 @@ func (c *ResultsVerifierCircuit) VerifyDecryptionProofs(api frontend.API) {
110113
}
111114

112115
func (c *ResultsVerifierCircuit) VerifyResults(api frontend.API) {
116+
bjjOrderMinusOne := new(big.Int).Sub(curves.New(bjj.CurveType).Order(), big.NewInt(1))
117+
113118
// Verify that the results add minus results sub equals results
114119
for i := range params.FieldsPerBallot {
120+
api.AssertIsLessOrEqual(c.AddAccumulators[i], bjjOrderMinusOne)
121+
api.AssertIsLessOrEqual(c.SubAccumulators[i], bjjOrderMinusOne)
115122
api.AssertIsLessOrEqual(c.SubAccumulators[i], c.AddAccumulators[i])
116123
api.AssertIsEqual(
117124
api.Sub(c.AddAccumulators[i], c.SubAccumulators[i]),

circuits/results/results_test.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,33 @@ func TestResultsVerifierCircuit(t *testing.T) {
151151
invalid := *assignment
152152
invalid.DecryptionAddProofs[0].A1.Y = big.NewInt(0)
153153

154-
// Start the proving process
154+
// subgroup order used by the ElGamal scalar arithmetic
155+
q := new(big.Int).Set(curves.New(bjj.CurveType).Order())
156+
157+
shiftedAssignment := func(slot int) *ResultsVerifierCircuit {
158+
honestAdd := new(big.Int).Set(addAccumulator[slot])
159+
honestSub := new(big.Int).Set(subAccumulator[slot])
160+
honestRes := new(big.Int).Sub(honestAdd, honestSub)
161+
162+
// Malicious witness: same ciphertext and same proof, but plaintext shifted by q.
163+
shifted := *assignment
164+
shifted.AddAccumulators[slot] = new(big.Int).Add(honestAdd, q)
165+
shifted.Results[slot] = new(big.Int).Add(honestRes, q)
166+
167+
c.Assert(shifted.Results[slot].(*big.Int).Cmp(assignment.Results[slot].(*big.Int)), qt.Not(qt.Equals), 0)
168+
169+
// Human-readable explanation of the bug
170+
c.Logf("[BUG DEMO] slot=%d\n honest result: %s\n shifted result: %s (= honest + subgroup order)",
171+
slot, honestRes.String(), shifted.Results[slot].(*big.Int).String())
172+
return &shifted
173+
}
155174
startTime = time.Now()
156175
assert.CheckCircuit(
157176
&ResultsVerifierCircuit{},
158177
test.WithValidAssignment(assignment),
159178
test.WithInvalidAssignment(&invalid),
179+
test.WithInvalidAssignment(shiftedAssignment(0)),
180+
test.WithInvalidAssignment(shiftedAssignment(1)),
160181
test.WithCurves(params.ResultsVerifierCurve),
161182
test.WithBackends(backend.GROTH16),
162183
)

state/testutil/state.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func NewRandomState(t *testing.T, origin types.CensusOrigin) *state.State {
4848
func NewVoteForTest(publicKey ecc.Point, voterIndex uint64, value int) *state.Vote {
4949
fields := [params.FieldsPerBallot]*big.Int{}
5050
for i := range fields {
51-
fields[i] = big.NewInt(int64(value + i))
51+
fields[i] = big.NewInt(int64(value * i))
5252
}
5353
ballot, err := elgamal.NewBallot(publicKey).Encrypt(fields, publicKey, nil)
5454
if err != nil {

0 commit comments

Comments
 (0)