defmodule Bbox.Votes do
import Ecto.Query
alias Bbox.Repo
alias Bbox.Votes.Vote
alias Bbox.Candidates.Candidate
alias Bbox.Voters
alias Bbox.Voters.Voter
@doc """
Insert new vote.
"""
@spec insert(binary(), %Candidate{}) :: {:ok, %Vote{}}
def insert(private_key, candidate) do
with %Voter{} = voter <- Voters.get_by_private_key!(private_key) do
private_key =
private_key
|> Base.decode32!()
|> X509.PrivateKey.from_pem!()
signature =
candidate.code
|> :public_key.sign(:sha256, private_key)
|> Base.encode32()
vote = %Vote{}
vote = Ecto.build_assoc(voter, :votes, vote)
vote = Ecto.build_assoc(candidate, :votes, vote)
vote = Vote.changeset(vote, %{signature: signature})
{:ok, vote} = Repo.insert(vote)
:telemetry.execute([:bbox, :votes, :insert], %{vote: vote})
{:ok, vote}
end
end
@doc """
Validate vote signature.
"""
@spec is_valid?(binary(), %Vote{}) :: {:ok, %Vote{}} | {:error, charlist()}
def is_valid?(private_key, vote) do
public_key =
private_key
|> Base.decode32!()
|> X509.PrivateKey.from_pem!()
|> X509.PublicKey.derive()
vote = Repo.preload(vote, [:voter, :candidate])
signature = Base.decode32!(vote.signature)
is_valid = :public_key.verify(vote.candidate.code, :sha256, signature, public_key)
:telemetry.execute([:bbox, :votes, :is_valid?], %{
is_valid: is_valid,
signature: signature,
public_key: public_key,
vote: vote
})
is_valid
end
@doc """
List votes.
"""
@spec list!() :: [%Vote{}]
def list!() do
votes =
Vote
|> Repo.all()
:telemetry.execute([:bbox, :votes, :list!], %{votes: votes})
votes
end
end