-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathvote.go
More file actions
276 lines (262 loc) · 8.9 KB
/
vote.go
File metadata and controls
276 lines (262 loc) · 8.9 KB
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
package api
import (
"encoding/hex"
"encoding/json"
"errors"
"net/http"
"github.com/ethereum/go-ethereum/common"
"github.com/go-chi/chi/v5"
"github.com/vocdoni/davinci-node/circuits/ballotproof"
"github.com/vocdoni/davinci-node/crypto/csp"
bjj "github.com/vocdoni/davinci-node/crypto/ecc/bjj_gnark"
"github.com/vocdoni/davinci-node/crypto/signatures/ethereum"
"github.com/vocdoni/davinci-node/log"
"github.com/vocdoni/davinci-node/state"
"github.com/vocdoni/davinci-node/storage"
"github.com/vocdoni/davinci-node/types"
"github.com/vocdoni/davinci-node/util"
"github.com/vocdoni/davinci-node/util/circomgnark"
)
// voteStatus returns the status of a vote for a given processID and voteID
// GET /votes/{processId}/voteId/{voteId}
func (a *API) voteStatus(w http.ResponseWriter, r *http.Request) {
// Get the processID and voteID from the URL
processID, err := types.HexStringToProcessID(chi.URLParam(r, ProcessURLParam))
if err != nil {
ErrMalformedProcessID.Withf("could not decode process ID: %v", err).Write(w)
return
}
voteID, err := hex.DecodeString(util.TrimHex(chi.URLParam(r, VoteIDURLParam)))
if err != nil {
ErrMalformedBody.Withf("could not decode vote ID: %v", err).Write(w)
return
}
// Get the vote ID status
status, err := a.storage.VoteIDStatus(processID, voteID)
if err != nil {
if errors.Is(err, storage.ErrNotFound) {
ErrResourceNotFound.WithErr(err).Write(w)
return
}
ErrGenericInternalServerError.WithErr(err).Write(w)
return
}
response := VoteStatusResponse{
Status: storage.VoteIDStatusName(status),
}
httpWriteJSON(w, response)
}
// voteByAddress retrieves an encrypted ballot by its address for a given
// processID
// GET /votes/{processId}/address/{address}
func (a *API) voteByAddress(w http.ResponseWriter, r *http.Request) {
// Get the processID
processID, err := types.HexStringToProcessID(chi.URLParam(r, ProcessURLParam))
if err != nil {
ErrMalformedProcessID.Withf("could not decode process ID: %v", err).Write(w)
return
}
// Get the address
address, err := types.HexStringToHexBytes(chi.URLParam(r, AddressURLParam))
if err != nil {
ErrMalformedAddress.Write(w)
return
}
// Open the state for the process
s, err := state.New(a.storage.StateDB(), processID)
if err != nil {
ErrProcessNotFound.Withf("could not open state: %v", err).Write(w)
return
}
defer func() {
if err := s.Close(); err != nil {
log.Warnw("could not close state", "processID", processID.String(), "error", err.Error())
}
}()
// Get the ballot by address
ballot, err := s.EncryptedBallot(address.BigInt().MathBigInt())
if err != nil {
if errors.Is(err, state.ErrKeyNotFound) {
ErrResourceNotFound.Write(w)
return
}
ErrGenericInternalServerError.Withf("could not get commitment: %v", err).Write(w)
return
}
httpWriteJSON(w, ballot)
}
// newVote creates a new vote and pushes it to the sequencer queue
// POST /votes
func (a *API) newVote(w http.ResponseWriter, r *http.Request) {
// decode the vote
vote := &Vote{}
if err := json.NewDecoder(r.Body).Decode(vote); err != nil {
ErrMalformedBody.Withf("could not decode request body: %v", err).Write(w)
return
}
// sanity checks
if vote.Ballot == nil || vote.BallotInputsHash == nil ||
vote.Address == nil || vote.Signature == nil {
ErrMalformedBody.Withf("missing required fields").Write(w)
return
}
if !vote.Ballot.Valid() {
ErrMalformedBody.Withf("invalid ballot").Write(w)
return
}
// get the process from the storage
process, err := a.storage.Process(vote.ProcessID)
if err != nil {
ErrResourceNotFound.Withf("could not get process: %v", err).Write(w)
return
}
// overwrite census origin with the process one to avoid inconsistencies
// and check the census proof with it
vote.CensusProof.CensusOrigin = process.Census.CensusOrigin
// validate the census origin
if !vote.CensusProof.CensusOrigin.Valid() {
ErrMalformedBody.Withf("invalid process census origin").Write(w)
return
}
// validate the census proof
if !vote.CensusProof.Valid() {
ErrMalformedBody.Withf("invalid census proof").Write(w)
return
}
// check that the process is ready to accept votes, it does not mean that
// the vote will be accepted, but it is a precondition to accept the vote,
// for example, if the process is not in this sequencer, the vote will be
// rejected
if ok, err := a.storage.ProcessIsAcceptingVotes(vote.ProcessID); !ok {
if err != nil {
ErrProcessNotAcceptingVotes.WithErr(err).Write(w)
return
}
ErrProcessNotAcceptingVotes.Write(w)
return
}
// check if the address has already voted, to determine if the vote is an
// overwrite or a new vote, if so check if the process has reached max
// voters
isOverwrite, err := state.HasAddressVoted(a.storage.StateDB(), *process.ID, process.StateRoot, vote.Address.BigInt())
if err != nil {
ErrGenericInternalServerError.Withf("error checking if address has voted: %v", err).Write(w)
return
}
if !isOverwrite {
if maxVotersReached, err := a.storage.ProcessMaxVotersReached(vote.ProcessID); err != nil {
ErrGenericInternalServerError.Withf("could not check max voters: %v", err).Write(w)
return
} else if maxVotersReached {
ErrProcessMaxVotersReached.Write(w)
return
}
}
// verify the census proof accordingly to the census origin and get the
// voter weight
var voterWeight *types.BigInt
switch {
case process.Census.CensusOrigin.IsMerkleTree():
// load the census from the census DB
censusRef, err := a.storage.CensusDB().LoadByRoot(process.Census.CensusRoot)
if err != nil {
ErrGenericInternalServerError.Withf("could not load census: %v", err).Write(w)
return
}
// verify the census proof
weight, exists := censusRef.Tree().GetWeight(common.BytesToAddress(vote.Address))
if !exists {
ErrInvalidCensusProof.Withf("address not in census").Write(w)
return
}
// overwrite the voter weight with the one from the census
voterWeight = new(types.BigInt).SetBigInt(weight)
case process.Census.CensusOrigin.IsCSP():
if err := csp.VerifyCensusProof(&vote.CensusProof); err != nil {
ErrInvalidCensusProof.Withf("census proof verification failed").WithErr(err).Write(w)
return
}
voterWeight = vote.CensusProof.Weight
default:
ErrInvalidCensusProof.Withf("unsupported census origin").Write(w)
return
}
// calculate the ballot inputs hash
ballotInputsHash, err := ballotproof.BallotInputsHash(
vote.ProcessID,
process.BallotMode,
new(bjj.BJJ).SetPoint(process.EncryptionKey.X.MathBigInt(), process.EncryptionKey.Y.MathBigInt()),
vote.Address,
vote.VoteID.BigInt(),
vote.Ballot.FromTEtoRTE(),
voterWeight,
)
if err != nil {
ErrGenericInternalServerError.Withf("could not calculate ballot inputs hash: %v", err).Write(w)
return
}
if vote.BallotInputsHash.String() != ballotInputsHash.String() {
ErrInvalidBallotInputsHash.Withf("ballot inputs hash mismatch").Write(w)
return
}
// load the verification key for the ballot proof circuit, used by the user
// to generate a proof of a valid ballot
if err := ballotproof.Artifacts.LoadAll(); err != nil {
ErrGenericInternalServerError.Withf("could not load artifacts: %v", err).Write(w)
return
}
// convert the circom proof to gnark proof and verify it
proof, err := circomgnark.VerifyAndConvertToRecursion(
ballotproof.Artifacts.RawVerifyingKey(),
vote.BallotProof,
[]string{vote.BallotInputsHash.String()},
)
if err != nil {
ErrInvalidBallotProof.Withf("could not verify and convert proof: %v", err).Write(w)
return
}
// verify the signature of the vote
signature := new(ethereum.ECDSASignature).SetBytes(vote.Signature)
if signature == nil {
ErrMalformedBody.Withf("could not decode signature: %v", err).Write(w)
return
}
signatureOk, pubkey := signature.Verify(vote.VoteID, common.BytesToAddress(vote.Address))
if !signatureOk {
ErrInvalidSignature.Write(w)
return
}
// Create the ballot object
ballot := &storage.Ballot{
ProcessID: vote.ProcessID,
VoterWeight: voterWeight.MathBigInt(),
// convert the ballot from TE (circom) to RTE (gnark)
EncryptedBallot: vote.Ballot.FromTEtoRTE(),
Address: vote.Address.BigInt().MathBigInt(),
BallotInputsHash: vote.BallotInputsHash.MathBigInt(),
BallotProof: proof.Proof,
Signature: signature,
CensusProof: &vote.CensusProof,
PubKey: pubkey,
VoteID: vote.VoteID,
}
// push the ballot to the sequencer storage queue to be verified, aggregated
// and published. The address locking is handled atomically inside PushPendingBallot
if err := a.storage.PushPendingBallot(ballot); err != nil {
switch {
case errors.Is(err, storage.ErroBallotAlreadyExists):
ErrBallotAlreadySubmitted.Write(w)
return
case errors.Is(err, storage.ErrNullifierProcessing):
ErrBallotAlreadyProcessing.Write(w)
return
case errors.Is(err, storage.ErrAddressProcessing):
ErrAddressAlreadyProcessing.Write(w)
return
default:
ErrGenericInternalServerError.Withf("could not push ballot: %v", err).Write(w)
return
}
}
httpWriteOK(w)
}