party/internal/data/votes.go

103 lines
2.2 KiB
Go

package data
import (
"errors"
"time"
"database/sql"
"math/big"
"context"
"party.at/party/internal/crypto"
"github.com/lib/pq"
)
var (
ErrDuplicateBlindSign = errors.New("user has already requested a blind signature for this issue")
ErrInvalidBlindedVote = errors.New("blinded_vote is out of valid range [1, n-1]")
ErrInvalidSignature = errors.New("signature verification failed")
ErrDuplicateVote = errors.New("this signature has already been used to cast a vote")
)
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
}