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 }