/// <summary> /// Deposit a ballot. /// </summary> /// <param name="ballot">Ballot in signed envleope.</param> /// <returns>Vote receipt signed by the server.</returns> public Signed<VoteReceipt> Vote( IRpcConnection connection, Signed<Envelope> signedEnvelope) { if (signedEnvelope == null) throw new ArgumentNullException("ballot"); if (Status != VotingStatus.Voting) { Logger.Log(LogLevel.Warning, "Connection {0}: Voter id {1} (unverified) tried to vote, but status was {2}.", connection.Id, signedEnvelope.Certificate.Id.ToString(), Status.ToString()); throw new PiArgumentException(ExceptionCode.WrongStatusForOperation, "Wrong status for operation."); } if (!signedEnvelope.Verify(this.certificateStorage)) { if (signedEnvelope.VerifySimple()) { Logger.Log(LogLevel.Warning, "Connection {0}: Voter id {1} (unverified) tried to vote, but his certificate had state {2}.", connection.Id, signedEnvelope.Certificate.Id.ToString(), signedEnvelope.Certificate.Validate(this.certificateStorage).Text()); } else { Logger.Log(LogLevel.Warning, "Connection {0}: Voter id {1} (unverified) tried to vote, but the signature on envelope was invalid.", connection.Id, signedEnvelope.Certificate.Id.ToString()); } throw new PiArgumentException(ExceptionCode.VoteSignatureNotValid, "Vote signature not valid."); } if (!(signedEnvelope.Certificate is VoterCertificate)) { Logger.Log(LogLevel.Warning, "Connection {0}: Voter id {1} (verified) tried to vote, but his certificate was not a voter certificate.", connection.Id, signedEnvelope.Certificate.Id.ToString()); throw new PiArgumentException(ExceptionCode.NoVoterCertificate, "Not a voter certificate."); } if (Parameters.GroupId != ((VoterCertificate)signedEnvelope.Certificate).GroupId) { Logger.Log(LogLevel.Warning, "Connection {0}: Voter id {1} (verified) tried to vote, but his group id was {2} when it should have been {3}.", connection.Id, signedEnvelope.Certificate.Id.ToString(), ((VoterCertificate)signedEnvelope.Certificate).GroupId, Parameters.GroupId); throw new PiArgumentException(ExceptionCode.BadGroupIdInCertificate, "Wrong group id in certificate."); } var envelope = signedEnvelope.Value; if (envelope.Date.Subtract(DateTime.Now).TotalHours < -1d || envelope.Date.Subtract(DateTime.Now).TotalHours > 1d) { Logger.Log(LogLevel.Warning, "Connection {0}: Voter id {1} (verified) tried to vote, but the envelope was created at {2} and pushed at {3}.", connection.Id, signedEnvelope.Certificate.Id.ToString(), envelope.Date.ToString(), DateTime.Now.ToString()); throw new PiArgumentException(ExceptionCode.InvalidEnvelopeBadDateTime, "Invalid envelope. Date out of range."); } if (envelope.VoterId != signedEnvelope.Certificate.Id) { Logger.Log(LogLevel.Warning, "Connection {0}: Voter id {1} (verified) tried to vote, but the envelope voter id did not match his certificate.", connection.Id, signedEnvelope.Certificate.Id.ToString()); throw new PiArgumentException(ExceptionCode.InvalidEnvelopeBadVoterId, "Invalid envelope. Voter id does not match."); } if (envelope.Ballots.Count != Parameters.Questions.Count()) { Logger.Log(LogLevel.Warning, "Connection {0}: Voter id {1} (verified) tried to vote, but there were {2} ballots in the envelope for {3} questions.", connection.Id, signedEnvelope.Certificate.Id.ToString(), envelope.Ballots.Count, Parameters.Questions.Count()); throw new PiArgumentException(ExceptionCode.InvalidEnvelopeBadBallotCount, "Invalid envelope. Ballot count does not match."); } for (int questionIndex = 0; questionIndex < parameters.Questions.Count(); questionIndex++) { var ballot = envelope.Ballots[questionIndex]; var question = parameters.Questions.ElementAt(questionIndex); if (ballot.SumProves.Count != parameters.ProofCount) { Logger.Log(LogLevel.Warning, "Connection {0}: Voter id {1} (verified) tried to vote, but there were {2} sum proofs present where there sould have been {3}.", connection.Id, signedEnvelope.Certificate.Id.ToString(), ballot.SumProves.Count, parameters.ProofCount); throw new PiArgumentException(ExceptionCode.InvalidEnvelopeBadProofCount, "Invalid envelope. Number of sum prooves does not match."); } if (ballot.Votes.Count != question.Options.Count()) { Logger.Log(LogLevel.Warning, "Connection {0}: Voter id {1} (verified) tried to vote, but there were {2} votes present for {3} options.", connection.Id, signedEnvelope.Certificate.Id.ToString(), ballot.Votes.Count, question.Options.Count()); throw new PiArgumentException(ExceptionCode.InvalidEnvelopeBadVoteCount, "Invalid envelope. Vote count does not match."); } if (ballot.Votes.Any(vote => vote.RangeProves.Count != parameters.ProofCount)) { Logger.Log(LogLevel.Warning, "Connection {0}: Voter id {1} (verified) tried to vote, but there was the wrong number of range proofs on a vote.", connection.Id, signedEnvelope.Certificate.Id.ToString()); throw new PiArgumentException(ExceptionCode.InvalidEnvelopeBadProofCount, "Invalid envelope. Number of range prooves does not match."); } } bool hasVoted = DbConnection.ExecuteHasRows( "SELECT count(*) FROM envelope WHERE VotingId = @VotingId AND VoterId = @VoterId", "@VotingId", Id.ToByteArray(), "@VoterId", signedEnvelope.Certificate.Id.ToByteArray()); if (hasVoted) throw new PiArgumentException(ExceptionCode.AlreadyVoted, "Voter has already voted."); MySqlTransaction transaction = DbConnection.BeginTransaction(); MySqlCommand indexCommand = new MySqlCommand( "SELECT max(EnvelopeIndex) + 1 FROM envelope WHERE VotingId = @VotingId", DbConnection, transaction); indexCommand.Add("@VotingId", Id.ToByteArray()); object indexObject = indexCommand.ExecuteScalar(); int envelopeIndex = indexObject == DBNull.Value ? 1 : Convert.ToInt32(indexObject); MySqlCommand insertCommand = new MySqlCommand( "INSERT INTO envelope (VotingId, EnvelopeIndex, VoterId, Value) VALUES (@VotingId, @EnvelopeIndex, @VoterId, @Value)", DbConnection, transaction); insertCommand.Add("@VotingId", Id.ToByteArray()); insertCommand.Add("@VoterId", signedEnvelope.Certificate.Id.ToByteArray()); insertCommand.Add("@Value", signedEnvelope.ToBinary()); insertCommand.Add("@EnvelopeIndex", envelopeIndex); insertCommand.ExecuteNonQuery(); transaction.Commit(); Logger.Log(LogLevel.Info, "Connection {0}: Envelope for certificate id {1} on voting id {2} stored.", connection.Id, signedEnvelope.Certificate.Id.ToString(), Id.ToString()); VoteReceipt voteReceipt = new VoteReceipt(Parameters, signedEnvelope); return new Signed<VoteReceipt>(voteReceipt, this.serverCertificate); }
/// <summary> /// Deposit partial deciphers from an authority. /// </summary> /// <param name="signedPartialDecipherList">Partial decipher list.</param> public void DepositPartialDecipher( IRpcConnection connection, Signed<PartialDecipherList> signedPartialDecipherList) { if (signedPartialDecipherList == null) throw new ArgumentNullException("partialDecipherContainer"); if (Status != VotingStatus.Deciphering) { Logger.Log(LogLevel.Warning, "Authority id {0} (unverified) tried to deposit his partial decipher, but the status was {1}.", signedPartialDecipherList.Certificate.Id.ToString(), Status.ToString()); throw new InvalidOperationException("Wrong status for operation."); } PartialDecipherList partialDecipherList = signedPartialDecipherList.Value; Certificate certificate = GetAuthority(partialDecipherList.AuthorityIndex); if (!signedPartialDecipherList.Verify(this.certificateStorage, Parameters.VotingBeginDate)) { if (signedPartialDecipherList.VerifySimple()) { Logger.Log(LogLevel.Warning, "Authority id {0} (unverified) tried to deposit his partial decipher, but his certificate had state {1}.", signedPartialDecipherList.Certificate.Id.ToString(), signedPartialDecipherList.Certificate.Validate(this.certificateStorage, Parameters.VotingBeginDate).Text()); } else { Logger.Log(LogLevel.Warning, "Authority id {0} (unverified) tried to deposit his partial decipher, but the signature was invalid.", signedPartialDecipherList.Certificate.Id.ToString()); } throw new PiArgumentException(ExceptionCode.InvalidSignature, "Bad signature."); } if (!signedPartialDecipherList.Certificate.IsIdentic(certificate)) { Logger.Log(LogLevel.Warning, "Authority id {0} (verified) tried to deposit his partial decipher, but his certificate did not match id {1} for authority index {2}.", signedPartialDecipherList.Certificate.Id.ToString(), certificate.Id.ToString(), partialDecipherList.AuthorityIndex); throw new PiArgumentException(ExceptionCode.AuthorityInvalid, "Not signed by proper authority."); } if (partialDecipherList.PartialDeciphers.Count < 4) { Logger.Log(LogLevel.Warning, "Authority id {0} (verified) tried to deposit his partial decipher, but there were only {1} parts instead of 4.", signedPartialDecipherList.Certificate.Id.ToString(), partialDecipherList.PartialDeciphers.Count); throw new PiArgumentException(ExceptionCode.AuthorityCountMismatch, "Your Pi-Vote client is outdated."); } bool exists = DbConnection.ExecuteHasRows( "SELECT count(*) FROM deciphers WHERE VotingId = @VotingId AND AuthorityIndex = @AuthorityIndex", "@VotingId", Id.ToByteArray(), "@AuthorityIndex", partialDecipherList.AuthorityIndex); if (exists) throw new ArgumentException("Authority has already deposited shares."); MySqlCommand insertCommand = new MySqlCommand("INSERT INTO deciphers (VotingId, AuthorityIndex, Value) VALUES (@VotingId, @AuthorityIndex, @Value)", DbConnection); insertCommand.Add("@VotingId", Id.ToByteArray()); insertCommand.Add("@AuthorityIndex", partialDecipherList.AuthorityIndex); insertCommand.Add("@Value", signedPartialDecipherList.ToBinary()); insertCommand.ExecuteNonQuery(); Logger.Log(LogLevel.Info, "Connection {0}: Partical deciphers for certificate id {1} on voting id {2} stored.", connection.Id, signedPartialDecipherList.Certificate.Id.ToString(), Id.ToString()); long depositedShareResponseCount = (long)DbConnection.ExecuteScalar( "SELECT count(*) FROM deciphers WHERE VotingId = @VotingId", "@VotingId", Id.ToByteArray()); SendAdminAuthorityActivityMail(certificate, "deposited partial deciphers"); if (depositedShareResponseCount == this.parameters.Thereshold + 1) { Status = VotingStatus.Finished; } }
/// <summary> /// Deposit a share part from authorities. /// </summary> /// <param name="signedSharePart">Share part.</param> public void DepositShares( IRpcConnection connection, Signed<SharePart> signedSharePart) { if (signedSharePart == null) throw new ArgumentNullException("shares"); if (Status != VotingStatus.New) { Logger.Log(LogLevel.Warning, "Connection {0}: Authority id {1} (unverified) tried to deposit shares, but the status was {2}.", connection.Id, signedSharePart.Certificate.Id.ToString(), Status.ToString()); throw new PiArgumentException(ExceptionCode.WrongStatusForOperation, "Wrong status for operation."); } SharePart sharePart = signedSharePart.Value; Certificate certificate = GetAuthority(sharePart.AuthorityIndex); if (!signedSharePart.Verify(this.certificateStorage, Parameters.VotingBeginDate)) { if (signedSharePart.VerifySimple()) { Logger.Log(LogLevel.Warning, "Connection {0}: Authority id {1} (unverified) tried to deposit shares, but his certificate had state {2}.", connection.Id, signedSharePart.Certificate.Id.ToString(), signedSharePart.Certificate.Validate(this.certificateStorage, Parameters.VotingBeginDate).Text()); } else { Logger.Log(LogLevel.Warning, "Connection {0}: Authority id {1} (unverified) tried to deposit shares, but the signature was invalid.", connection.Id, signedSharePart.Certificate.Id.ToString()); } throw new PiSecurityException(ExceptionCode.InvalidSignature, "Bad signature."); } if (!signedSharePart.Certificate.IsIdentic(certificate)) { Logger.Log(LogLevel.Warning, "Connection {0}: Authority id {1} (verified) tried to deposit shares, but his certificate did not match id {2} for authority index {3}.", connection.Id, signedSharePart.Certificate.Id.ToString(), certificate.Id.ToString(), sharePart.AuthorityIndex); throw new PiSecurityException(ExceptionCode.NoAuthorizedAuthority, "Not signed by proper authority."); } bool exists = DbConnection.ExecuteHasRows( "SELECT count(*) FROM sharepart WHERE VotingId = @VotingId AND AuthorityIndex = @AuthorityIndex", "@VotingId", Id.ToByteArray(), "@AuthorityIndex", sharePart.AuthorityIndex); if (exists) { Logger.Log(LogLevel.Warning, "Connection {0}: uthority id {1} (verified) tried to deposit shares, these were already present.", connection.Id, signedSharePart.Certificate.Id.ToString()); throw new PiArgumentException(ExceptionCode.AuthorityHasAlreadyDeposited, "Authority has already deposited shares."); } MySqlCommand insertCommand = new MySqlCommand("INSERT INTO sharepart (VotingId, AuthorityIndex, Value) VALUES (@VotingId, @AuthorityIndex, @Value)", DbConnection); insertCommand.Add("@VotingId", Id.ToByteArray()); insertCommand.Add("@AuthorityIndex", sharePart.AuthorityIndex); insertCommand.Add("@Value", signedSharePart.ToBinary()); insertCommand.ExecuteNonQuery(); Logger.Log(LogLevel.Info, "Connection {0}: Shares for certificate id {1} on voting id {2} stored.", connection.Id, signedSharePart.Certificate.Id.ToString(), Id.ToString()); long depositedSharePartCount = (long)DbConnection.ExecuteScalar( "SELECT count(*) FROM sharepart WHERE VotingId = @VotingId", "@VotingId", Id.ToByteArray()); SendAdminAuthorityActivityMail(certificate, "deposited shares"); if (depositedSharePartCount == this.parameters.AuthorityCount) { Status = VotingStatus.Sharing; } }
/// <summary> /// Add an authority. /// </summary> /// <param name="certificate">Authority to be added.</param> /// <returns>Index of the authority.</returns> public int AddAuthority( IRpcConnection connection, Certificate certificate) { if (certificate == null) throw new ArgumentNullException("certificate"); if (certificate.Validate(this.certificateStorage) != CertificateValidationResult.Valid) throw new PiSecurityException(ExceptionCode.InvalidCertificate, "Authority certificate not valid."); if (!(certificate is AuthorityCertificate)) throw new PiSecurityException(ExceptionCode.NoAuthorizedAuthority, "No an authority certificate."); MySqlTransaction transaction = DbConnection.BeginTransaction(); MySqlCommand countCommand = new MySqlCommand("SELECT count(*) FROM authority WHERE VotingId = @VotingId", DbConnection, transaction); countCommand.Add("@VotingId", this.parameters.VotingId.ToByteArray()); if ((long)countCommand.ExecuteScalar() >= this.parameters.AuthorityCount) throw new PiArgumentException(ExceptionCode.AlreadyEnoughAuthorities, "Already enough authorities."); MySqlCommand addedCommand = new MySqlCommand("SELECT count(*) FROM authority WHERE VotingId = @VotingId AND AuthorityId = @AuthorityId", DbConnection, transaction); addedCommand.Add("@VotingId", this.parameters.VotingId.ToByteArray()); addedCommand.Add("@AuthorityId", certificate.Id.ToByteArray()); if (addedCommand.ExecuteHasRows()) throw new PiArgumentException(ExceptionCode.AuthorityAlreadyInVoting, "Already an authority of the voting."); MySqlCommand indexCommand = new MySqlCommand("SELECT max(AuthorityIndex) + 1 FROM authority WHERE VotingId = @VotingId", DbConnection, transaction); indexCommand.Add("@VotingId", this.parameters.VotingId.ToByteArray()); object authorityIndexNull = indexCommand.ExecuteScalar(); int authorityIndex = authorityIndexNull == DBNull.Value ? 1 : Convert.ToInt32((long)authorityIndexNull); MySqlCommand insertCommand = new MySqlCommand("INSERT INTO authority (VotingId, AuthorityIndex, AuthorityId, Certificate) VALUES (@VotingId, @AuthorityIndex, @AuthorityId, @Certificate)", DbConnection, transaction); insertCommand.Parameters.AddWithValue("@VotingId", this.parameters.VotingId.ToByteArray()); insertCommand.Parameters.AddWithValue("@AuthorityIndex", authorityIndex); insertCommand.Parameters.AddWithValue("@AuthorityId", certificate.Id.ToByteArray()); insertCommand.Parameters.AddWithValue("@Certificate", certificate.ToBinary()); insertCommand.ExecuteNonQuery(); Logger.Log(LogLevel.Info, "Connection {0}: Authority id {1} added to voting id {2}", connection.Id, certificate.Id.ToString(), Id.ToString()); transaction.Commit(); return authorityIndex; }