94 lines
1.8 KiB
Go
94 lines
1.8 KiB
Go
package data
|
|
|
|
import (
|
|
"time"
|
|
"database/sql"
|
|
"math/big"
|
|
"context"
|
|
|
|
"party.at/party/internal/crypto"
|
|
|
|
"github.com/lib/pq"
|
|
)
|
|
|
|
type Vote struct {
|
|
ID int64 `json:"id"`
|
|
OptionID int64 `json:"option_id"`
|
|
Nonce []byte `json:"nonce"`
|
|
Signature []byte `json:"signature"`
|
|
Created time.Time `json:"created"`
|
|
}
|
|
|
|
type VoteModel struct {
|
|
DB *sql.DB
|
|
}
|
|
|
|
|
|
func (m VoteModel) CountForOption(optionID int64) (int64, error) {
|
|
query := `
|
|
SELECT COUNT(v.id)
|
|
FROM options o
|
|
LEFT JOIN votes v ON v.option_id = o.id
|
|
WHERE o.id = $1
|
|
GROUP BY o.id`
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
|
|
defer cancel()
|
|
|
|
var count int64
|
|
|
|
err := m.DB.QueryRowContext(ctx, query, optionID).Scan(&count)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, err
|
|
}
|
|
|
|
func (m VoteModel) Insert(vote *Vote, issue *Issue) error {
|
|
|
|
n := new(big.Int).SetBytes(issue.N)
|
|
e := issue.E
|
|
|
|
// Recompute m = SHA-256(issueID || optionID || nonce)
|
|
expected := crypto.VoteMessage(issue.ID, vote.OptionID, vote.Nonce)
|
|
|
|
// Verify: s^e mod n == m
|
|
sig := new(big.Int).SetBytes(vote.Signature)
|
|
eInt := big.NewInt(int64(e))
|
|
recovered := new(big.Int).Exp(sig, eInt, n)
|
|
expectedInt := new(big.Int).SetBytes(expected[:])
|
|
|
|
if recovered.Cmp(expectedInt) != 0 {
|
|
return ErrInvalidSignature
|
|
}
|
|
|
|
query :=
|
|
`INSERT INTO votes (option_id, nonce, signature)
|
|
VALUES ($1, $2, $3)
|
|
RETURNING id, created`
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancel()
|
|
|
|
args := []interface{}{
|
|
vote.OptionID,
|
|
vote.Nonce,
|
|
vote.Signature,
|
|
}
|
|
|
|
err := m.DB.QueryRowContext(ctx, query, args...).Scan(&vote.ID, &vote.Created)
|
|
if pgErr, ok := err.(*pq.Error); ok {
|
|
if pgErr.Code == "23505" {
|
|
if pgErr.Constraint == "votes_signature_key" {
|
|
return ErrDuplicateVote
|
|
}
|
|
}
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|