SecurePoll

SecurePoll is a voting system with per-election configuration stored in the database. It was originally designed for use in Board elections, but more recently it has been used for a number of other elections.
== About XML files ==
Prior to 2014, there was no UI for creating or editing election configuration. However, there were CLI scripts to import and export the configuration as XML. Elections were created by manually writing XML files and then importing them. That's why cli/dump.php and cli/import.php are relatively complex, and why various old tools produce or act on XML.
== WMF-specific CLI scripts ==
A lot of scripts were written under extreme time pressure to do a very specific job per the requirements of a given election. Some were archived under in a year-specific directory under cli/wm-scripts, and then copied forward for each new election with minor modifications. Some scripts were apparently lost.
; dumpMetaTranslations.php : SecurePoll has its own translation UI. In the 2013 Board vote this was not used, instead messages were edited as pages on meta. This script generated compiled the translations on meta and generated XML for use with import.php --update-msgs. 
; doSpam.php : Collects voter names and email addresses, and outputs them as a tab-delimited text file. Used in 2013 and 2015.
; buildSpamTranslations.php : Extracts bulk email translations from meta and dumps them as a series of text files.
; sendEmails.php : Actually sends emails using files generated by doSpam.php and buildSpamTranslations.php.
; dumpGlobalVoterList.php: Similar to doSpam.php, probably used in 2009 and 2011. 
; populateEditCount.php : This script, copied forward with minor variations from 2013 to 2021, counts local edits selected according to various criteria and stores them in a table on the local wiki: bv2013_edits, bv2015_edits, etc.
; voterList.php: The 2013 version was run on each wiki to generate a local voter list. The 2015-2017 version is run once on a random wiki, and populates centralauth.securepoll_lists with a global voter list by adding together edit counts across all wikis.
; makeGlobalVoterList.php: A generic version of voterList.php introduced in 2021.
; makeArbcomList.php: Supposedly a script of this name was used for the 2009 ArbCom election.
== How to run a board election ==
=== Voter eligibility ===
It is necessary to run scripts to pre-compute eligibility lists. The scripts take about 2 days to run, though you should allow for a few weeks for patches to be merged.
The 2021 Board election voter eligibility rules for editors were:
You may vote from any one registered account you own on a Wikimedia wiki. You may only vote once, regardless of how many accounts you own. To qualify, this one account must:
* be blocked in no more than one project;
* and not be a bot;
* and have made at least 300 edits before 5 July 2021 across Wikimedia wikis;
* and have made at least 20 edits between 5 January 2021 and 5 July 2021.
To implement the global block count requirement:
* Go to the voter eligibility subpage, check "Must not be blocked on too many attached wikis", enter 2 for "Central block threshold". This sets the central-block-threshold property to 2.
* Uncheck "must not be sitewide blocked" -- this has been checked in previous years but seems to conflict with the block count requirement
The "not a bot" criterion is the not-bot property, which can be changed in the UI by checking "Must not be flagged as a bot". But note that this only filters by bot permissions on the jump wiki. 
The edit count criteria don't have a UI. There is phab:T288183 to track that. For now:
* Copy and adapt bv2022/populateEditCount.php and (optionally) bv2022/bv2022_tables.sql.
** mw:Gerrit/Tutorial/tl;dr is a useful guide on how to do this. Otherwise, ask for support from an engineer.
* Once the new files are merged, create the bvYYYY_edits table (where YYYY is the year) on all wikis. If updated, use bv2022/bv2022_tables.sql as a template. For example:
for db in $(expanddblist all); do
 echo 'CREATE TABLE `securepoll_bv2021_edits` (
 `bv_user` int(11) NOT NULL,
 `bv_long_edits` int(11) NOT NULL,
 `bv_short_edits` int(11) NOT NULL,
 PRIMARY KEY (`bv_user`)
)' | mwscript sql.php --wiki=$db
done
or
foreachwiki sql.php /srv/mediawiki-staging/php-1.38.0-wmf.1/extensions/SecurePoll/cli/wm-scripts/bv2021/bv2021_tables.sqlIf you get an error with the foreachwiki command, ensure you are using the most-recent php version currently available on the server, and that your patch has actually been merged.
* Once the beforeTime date has passed, run the new populateEditCount.php on all wikis. This takes about 2 days if you use one thread per section, i.e. foreachwikiindblist $section .../populateEditCount.php. It is recommended to do this in a screen. Note that as of March 2024 there are eight sections so do this in eight separate screens.
 section=s1
 foreachwikiindblist $section /srv/mediawiki-staging/php-1.38.0-wmf.1/extensions/SecurePoll/cli/wm-scripts/bv2021/populateEditCount.php
Again, if you get an error, ensure the php version is the latest one available and that it actually contains the merged scripts.
* Run makeGlobalVoterList.php on a random wiki. (This will take around 10 hours for an election this size, so also use a screen for this.) E.g.:
 mwscript extensions/SecurePoll/cli/wm-scripts/makeGlobalVoterList.php \
 --wiki=mediawikiwiki \
 --edit-count-table=securepoll_bv2021_edits \
 --list-name=board-vote-2021 \
 --short-min-edits=20 \
 --long-min-edits=300
* After election creation, manually insert into the database a property with name need-central-list and the value being the name of the list given to makeGlobalVoterList.php.
 $ sql votewiki --write
 wikiadmin@10.192.0.120(votewiki)> insert into securepoll_properties (pr_entity,pr_key,pr_value) values (1079,'need-central-list', 'board-vote-2021');
The 2021 rules require developers to automatically be granted access if they:
* are Wikimedia server administrators with shell access
* or have made at least one merged commit to any Wikimedia repos on Gerrit, between 5 January 2021 and 5 July 2021.
It is unclear how this can be implemented. The wording was introduced in June 2021 and was discussed at phab:T281977.
==== Exceptions ====
Exceptions that come from fixed lists, such as staff exceptions, should be put into a flat file containing usernames only, with one name per line. Then on the maintenance server:
 mwscript extensions/SecurePoll/cli/wm-scripts/importGlobalVoterList.php --wiki=metawiki --list-name board-vote-YYYY special-users.txt | tee special-users.log
where YYYY is the year.
This script can be run multiple times to add different kinds of users.
=== Creating the election on votewiki ===
The SecurePoll interface on votewiki is fairly self explanatory, and most of the elements there can be changed at any time before the election. Some of them can also be changed during the election itself (notably, the list of election admins).
* Poll title
* For wiki (as in, which wiki is the election for? If it's a local election this will be the local wiki affected, for example fawikipedia for the Farsi Wikipedia ArbCom elections)
* Primary language (this does not change the language of the interface; a patch is needed for that. This is primarily for the native translations used by SecurePoll)
* Election start date and time (in UTC)
* Return-to URL: The URL that voters will click to "return" home after voting, usually the vote's home page on Meta-Wiki or the local wiki.
* Poll type: There are many options here, but the typical ones used are "range voting (histogram range)" (support / support + oppose), "Single transferable vote with Droop quota" (also known as Meek STV or Scottish STV), and "Schulze vote". Board elections as of 2021 use STV.
* Encryption: Elections are typically encrypted though some are not. If you'd like it encrypted it is recommended this be set up as the vote is being created on SecurePoll in this step, but it can happen at any point before the election opens.
* Various checkbox settings related to voters:
** Prevent voters from changing their votes: Disallows voters from voting again if they change their minds. Generally not enabled.
** Disable transparency features (e.g. voter list) to protect voter privacy: Hides the voter list from other voters. The voter list contains a list of users who have voted in the election but does not include their actual vote. This is sometimes enabled for local elections, but most major ones leave it disabled. 
** Shuffle questions on the voting page and Shuffle options on the voting page: A "question" is something like, "Board candidates" or "Please rank these candidates". An "option" is usually a candidate's name. There is almost never more than one question, so this can be left disabled. However, for fairness, the options are generally shuffled. 
** Require that all options be answered or Require that all options be ranked: This forces a voter to give their opinion on every candidate. It is not even an option for STV elections, and for others is almost never selected.
* Admins: The users listed here will be able to alter the election settings and, later, see the user data of those who have voted. For this reason it is imperative that they are either removed from the election before voting opens or appear on Access to nonpublic personal data policy/Noticeboard. Remember to add yourself here so that you too can alter the election settings later - you will not have that right automatically.
* Questions:
** Question text: Usually "Board candidates" or "Please rank these candidates" or something similar. This can be translated through the interface. This text is not to be confused with the into-text, which is not set in this form.
** Option text: Each textbox (more may be added) contains an option, usually a candidate, that voters can rank or support. These can be added later. They can also be translated, which is helpful if one later withdraws.
Once these options are specified, the election can be created (with a comment for the logs if you'd like to provide one).
=== Translations ===
Translations are added on a per-election basis through Special:SecurePoll/translate/n (where n is the election id). The following elements are usually translatable in a Board election and will typically have quite a lot of historical translations that can be copied and pasted:
* Election title (e.g. Wikimedia Foundation Board Elections 2021)
* Intro text (appears above the ballot, usually includes instructions on how to vote)
* Jump text (e.g. The vote will be conducted on a central wiki. Please click the button below to be transferred.)
* Return text (the text that appears in the phrase that appears after voting: Return to Wikimedia Foundation elections 2021 portal)
* Unqualified error (e.g. We apologize, but you do not appear to be on the eligible voter list...)
* Comment prompt (for elections with a comment box)
* Question text (e.g. Board candidates)
* Option text (candidate names)
A few gotchas, pitfalls and comments (from 2011-2013):
* Don't let your translators translate the section titles! It breaks the script that generates the XML file.
* When all the candidates have been submitted, get the translators to translate the "Candidate text" section. If the candidate names don't need to be transliterated (and can be displayed as in English), get them to remove that section entirely. If they do need to be transliterated (for languages such as Arabic), make sure that the candidates are in the right order.
* Magic words do not (currently) work. They need removing
=== Do a test run ===
A few days before the election, you should run a test election for a few days, just to make sure that everything (particularly eligibility and the session transfer) is working properly. To make sure that there is absolutely no risk of confusion, change every message in this election to "THIS IS A TEST ELECTION, DO NOT VOTE", or similar.
=== Email spam ===
Create a translatable base page for translations following the format at m:Wikimedia Foundation elections/2021/2021-08-27/Second board voter e-mail, specifically:
* Translatable sender name on a line starting with the literal text "From:"
* Subject in level 2 heading
* Wikitext for HTML email can contain external and interwiki links, but internal links won't work without a modification to the script. Anything depending on separate CSS stylesheets will not work — use style attributes.
* Plain text version in <pre>
Create the page well in advance. The top 10 user preference languages by number of qualified users in 2021 were: en, de, fr, ru, es, ja, it, pl, nl.
Find all global users who have the bot right on at least one wiki, so that they can be excluded from the list:
 year=2021
 foreachwikiindblist securepollglobal extensions/SecurePoll/cli/findUsersWithRight.php --central-list=bots-$year --right=bot | tee findUsersWithRight.log
Create a mailing list file for each wiki:
 year=2021
 section=s1
 for wiki in $(expanddblist $section); do
 echo $wiki
 mwscript extensions/SecurePoll/cli/wm-scripts/makeMailingList.php --wiki=$wiki --election='Wikimedia Foundation Board Elections 2021' --election-wiki=votewiki --exclude-central-list=bots-$year > ml-$wiki
 done
And similarly for each section s2-s8.
Then deduplicate the mailing list files:
 mwscript extensions/SecurePoll/cli/wm-scripts/deduplicateMailingList.php --wiki=metawiki ml-* > dedup-all
Test the mailout for all translated languages, e.g.
 for lang in en de fr ru es ja it pl nl; do
 printf "enwiki\tWikipedia\tTim Starling\ttstarling@wikimedia.org\t$lang\t0\n" >> lang-test
 done
 mwscript extensions/SecurePoll/cli/wm-scripts/sendMail.php --wiki=metawiki --page='Wikimedia Foundation elections/2021/2021-08-20/Board voter e-mail' --sender='board-elections@lists.wikimedia.org' lang-test
Do the actual mailout:
 mwscript extensions/SecurePoll/cli/wm-scripts/sendMail.php --wiki=metawiki --page='Wikimedia Foundation elections/2021/2021-08-20/Board voter e-mail' --sender='board-elections@lists.wikimedia.org' dedup-all | tee -a sendMail.log
The script reports the line number from the input file after sending each email, so if you need to restart the script, you can make a new mailing list removing the number of lines corresponding to the last reported line number.
Expect autoreply backscatter of about 100 emails.
You should be prepared to deal with the following types of complaints:
* Unflagged bots being invited to vote. Flagged ones are filtered as of changes made in ]
* Gaffes in translation and execution (in the case of 2011, the subject line wasn't updated). The test run will fix most of these, but you should also check that the translations aren't trying to use magic words or anything fancy (unless you add support to the mailout script). Also check on the formatting of the translations themselves, translators have a habit of changing things to make them clearer and breaking the scripts.
* A few lessons learned from 2011 that would be good to apply in future:
** Some users with multiple accounts got multiple emails — deduplication by email address is an option worth considering. This has now been implemented.
** We should send out the email reasonably early in the process — at most halfway through.
** Translators need to be briefed about format — in particular, magic words don't work in the plaintext version, and {{GENDER:}} doesn't work in either version.
See also MetaWikipedia:Image filter referendum/Email/False positives.
=== Duplicate removal ===
SecurePoll takes the approach of trying to make ballot stuffing detectable, and allowing it to be rectified if it is detected, rather than discouraging it with error messages. This reduces the risk that a malicious user will refine their methods until they are able to evade all technical restrictions. The tradeoff is that it creates a lot of work for election administrators.
(However, allowing the same CentralAuth user to vote twice from different wikis is technical debt arising from the introduction of CentralAuth, it is not a deliberate design decision.)
SecurePoll allows people to alter their votes by voting more than once. But if a person votes more than once with different accounts, or with the same username on different wikis, then those votes are counted twice. Such votes need to be manually removed. Election documentation should make it clear that each person is only entitled to vote once, and that having multiple qualified accounts is not a license to vote more than once. Policies should be put in place to strongly discourage deliberate ballot-stuffing.
During the setup process, two or three trusted, independent users should be enlisted as "scrutineers" and assigned as election administrators for that election. They should also be provided the electionadmin user right on vote.wikimedia.org to allow them to see the voter data (ideally with an expiry time two weeks or so after the vote closes).
For people who are named as election administrators on vote.wikimedia.org, SecurePoll extends the "list" page to provide a duplicate vote removal interface. As a first pass, sort it by username, and then scroll through the results looking for multiple votes with the same username, ignoring greyed-out entries. Then do the same sorting by IP address.
Votes with a "Duplicate" marker should be investigated. The details page gives the username of the suspected sockpuppet, based on cookie evidence. Any votes deemed to be duplicates should be struck out, by clicking the "strike" link. This will remove them from the tally entirely. Such voters whose votes are struck should be contacted by email, if they have set an email address in their preferences.
=== Encryption ===
Two separate keys need to be generated for each election: an encryption key and a signing key.
In the past, election committee members have been put under pressure to release running tallies, while voting is still in progress. On one occasion, such a tally was privately given to the interested party, and the results were used to influence voting in a last-ditch campaign. Having an outside party hold the encryption key exclusively until after the election is complete avoids this potential quandary. 
After voting is complete, the encryption key should be given to several trusted election committee members. To prevent vote-buying, the encryption key should never be publicly released. If the encryption key is released, then a voter can use their encrypted record to prove that they voted in a particular way.
Instead, voters wishing to establish the integrity of their encrypted election records should do so by private communication with a private key holder. The election record can be decrypted and compared with who the voter claims they voted for.
The signing key allows voters to prove that they have a valid encrypted record. If a voter has a signed voting record which is not present in the database, then this is clear evidence that the server has been compromised.
Key generation should be done by the third party keeping the decryption key. To generate each key, use these prompts on the maintenance server (be sure to use appropriately-specific names):
 openssl genpkey -algorithm rsa -out encrypt-private.pem
 openssl pkey -in encrypt-private.pem -pubout -out encrypt-public.pem
 openssl genpkey -algorithm rsa -out sign-private.pem
 openssl pkey -in sign-private.pem -pubout -out sign-public.pem
Keys can then be inserted through the SecurePoll interface on votewiki (the private signing key, and the public encryption key). The private encryption key is used to decrypt the election once it is time to tally.
If you wish to use GPG instead, use these prompts. This will not work on Wikimedia infrastructure.
 gpg --gen-key
Then follow the prompts. Use usernames like "Wikimedia Board Election 2011 signing key" and "Wikimedia Board Election 2011 encryption key" to make it easier to know which is which. Then export each key:
 gpg --armor --export-secret-keys 'Wikimedia Board Election 2011 signing key'
 gpg --armor --export 'Wikimedia Board Election 2011 encryption key'
=== Tallying ===
==== Simple method: GUI ====
Most elections can be tallied on the web interface. To access the tally interface, navigate to Special:SecurePoll on votewiki, find your poll, and click "Tally". If your election was not encrypted, this page should give you the results almost immediately.
If the election was encrypted, paste the decryption key (the private encryption key) into the labelled box and click "Create tally". After a few minutes, refreshing the page should provide the tally.
==== Alternative method: Command line ====
On the vote wiki side, export the election XML including votes with the following command ("election name" should be the exact name of the election as it exists on SecurePoll, encompassed by quotation marks):
 mwscript extensions/SecurePoll/cli/dump.php --wiki=votewiki "2019 English Wikipedia Arbitration Committee election" --votes > ACE2019votes.xml
Then, extract the private key which goes along with the public encryption key. This can be obtained by the third-party holder using the key name from earlier (note the use of "export-secret-keys" here):
 gpg --armor --export-secret-keys 'ArbCom Election 2019 encryption key'
Open the votes file in something like vim or nano:
 vi ACE2019votes.xml
Add the private key for decryption in a new parameter just below <property name="encrypt-type">gpg</property> like so (paste the key in place of "...exported secret key…"):
<property name="gpg-decrypt-key">...exported secret key...</property>
You will also need to add back the signing and encryption keys per phab:T297808.
Save the file (in vim, hit the Esc key, then type :wq and press enter).
Then use tally.php:
 mwscript extensions/SecurePoll/cli/tally.php --wiki=votewiki ACE2019votes.xml
(This will take a little while, so don't panic if nothing happens for a few minutes.)
You can then copy and paste the results into a new text file, save it as an HTML file, and then open it in a web browser.
If the election used range voting (histogram range), copy the table from there into a spreadsheet and use a formula to work out the overall support percentage (Support / Support+Oppose). Sort descending by this Support percentage, then pass that information onto the scrutineers and the commissioners for them to double-check and verify. The commissioners will post this information to the wiki as needed.
If the election used some other voting method (more recently, STV), you can still access the results in that HTML file, but there will be no need to clean up the data. Just copy and paste it into an email to the commissioners and scrutineers for their signoff.
== Session transfers ==
SecurePoll supports having an election stubbed out on the wiki that the voters actually come from, but hosted somewhere else (votewiki in this case).
Since 2014 these stub elections are created automatically by the election creation UI when you select the "all wikis" option.
Prior to 2014, the stub elections were set up by generating an XML configuration file and importing it into all wikis.
Users do not actually need an account on the wiki where the vote is actually hosted. This was used prior to 2013 to host the vote on servers not controlled by WMF. Even though we now self-host our elections, votewiki does not have CentralAuth enabled and so users will typically not have a user account when they vote.
Session transfer works by having the jump wiki send a token to votewiki. Votewiki authenticates the token by doing an HTTP request to auth-api.php. The auth-api.php request returns user parameters which are saved by votewiki. This mechanism predates the action API and there is a task to convert this mechanism to finally use the action API which may soon be implemented.
A config hack adds "lang" and "site" parameters to the jump URL, which the remote site uses to reconstruct a URL back to the jump wiki.
== Timeline ==
; 2004: The first Board election. Tim, then a volunteer, quickly wrote the BoardVote extension to run this election. It was one of the first MediaWiki extensions. Many of SecurePoll's quirks were already present, such as GPG-based encrypted vote receipts. The voting/tallying method was approval voting.
; 2005-2008: The BoardVote extension was edited for each election, changing table names etc. to make it continue to work for another year.
; 2007: The first election hosted by Software in the Public Interest (SPI). SPI would host the 2007, 2008, 2009 and 2011 elections.
; 2008: The tallying method was changed to the Schulze method (a Condorcet preferential method).
; 2009: Tim wrote the first version of SecurePoll, as a multi-election adaption of the BoardVote code. It was used for the 2009 Board election.
; 2013: Elections returned to being self-hosted. Encryption of voting records at rest effectively ended, with private (tallying) keys being stored on the server instead of held by an election administrator and tallied offline.
; 2013: The voting method was changed to a modified range vote formula: support/(support+oppose). 
; 2014: Brad wrote an election creation UI. But the UI continues to be incomplete and was not used for voter qualification in 2015-2021.
; 2021: The voting method was changed to Single Transferable Vote.
; 2024: Encryption was changed from GPG to OpenSSL.
Category:How-To
Category:MediaWiki production