SecurePoll

From Wikitech

SecurePoll is a voting system with per-election configuration stored in the database. We use it for Board elections.

How to send a message dump from Meta

On Wikimedia run (use tin):

mwscript  extensions/SecurePoll/cli/wm-scripts/dumpMetaTranslations.php --wiki=metawiki > some-file.xml

How to update the jump text on all Wikimedia wikis

Using an updated version of jumpWiki config run: foreachwiki extensions/SecurePoll/cli/import.php --update-msgs jumpwikiconfig.xml

How to create an Arbcom election on enwiki

The 2009 Arbcom election had voter qualification requirements of the form "X main-namespace edits before date Y". A script to calculate a list of qualified voters with criteria of this form is at SecurePoll/cli/wm-scripts/makeArbcomList.php on NFS. If this form of qualification is used again, run something like:

mwscript extensions/SecurePoll/cli/makeArbcomList.php --wiki=enwiki --before=<date> --edits=<edits> arbcom-2010

where "arbcom-2010" is the list name, which should be different for each election. The next task is to author the XML file. The XML for the 2009 election is at SecurePoll/cli/wm-setup/real-arbcom-2009.xml. Copy it to your local computer and open it in a text editor. Due to the nasty unfinished nature of the software, it's necessary to hard-code the entity IDs in the XML file. An entity is an election, question or option. To find out the highest allocated ID:

sql enwiki
select max(en_id) from securepoll_entity;

Round up to the nearest 10 and make that number the election ID. The election ID goes in the <id> element as a child of the <election> element. SecurePoll elections can have multiple questions. For Arbcom elections there is only one question. It should have an ID which is the election ID plus one. The question element is a child of the election element. Start with the XML file from the previous year. Update the <title>, <startDate>, <endDate> and <id> elements. Update the admin list. Election administrators should generally have CheckUser access, or be otherwise identified to the foundation under the privacy policy, since a SecurePoll administrator has access to user IP addresses for the purposes of fraud detection. Update the need-list property to be the same as the list name you used in makeArbcomList.php. Messages (in message elements) are editable after import by election administrators, so it's only necessary to put a sensible illustrative default in each message. Update the election title message. Delete the previous year's list of <option> elements as children of the <question> element, and create a new list. Each option is a candidate. They should have a message called "text" which is their name, and an <id> element with sequential IDs. Once the XML file is written, send it to the election administrators for review. You can test it locally by importing it into a local MediaWiki instance. After it is reviewed, copy it into the live wm-setup directory, and import it into the database with:

mwscript extensions/SecurePoll/cli/import.php --wiki=enwiki wm-setup/arbcom-[year].xml

The election will start automatically at the specified start date. After the end of the election, a web-based tallying interface will be enabled, with access for administrators. This web-based interface may time out if there were a lot of votes. In this case, do the tally from the command line, using:

mwscript extensions/SecurePoll/cli/tally.php --wiki=enwiki --name <election-name>

where the election name is the contents of the <title> element in the XML file.

Session transfers

SecurePoll supports having an election stubbed out on the wiki that the voters actually come from, but hosted somewhere else (loginwiki in this case). On the stubbed out wiki, you use these configuration lines to activate the jump:

<auth>local</auth>
<property name="jump-url">https://vote.wikimedia.org/wiki/Special:SecurePoll</property>
<property name="jump-id">200</property>

And on the remote wiki which is actually hosting the election, you use these lines:

<auth>remote-mw</auth>
<property name="remote-mw-script-path">https://vote.wikimedia.org/w</property>

You need to ensure that 'site' and 'lang' are passed as parameters by the jump form. The following code is already in place in CommonSettings.php:

$wgHooks['SecurePoll_JumpUrl'][] = 'wmfSecurePollJumpUrl';
function wmfSecurePollJumpUrl( $page, &$url ) {
    global $site, $lang;
    
    $url = wfAppendQuery( $url, array( 'site' => $site, 'lang' => $lang ) );
    return true;
}

Note that the users do not need an account on the remote wiki! The source wiki (with the jump election) sends the user to the remote wiki with some POST parameters — the site and language of the source wiki, the user's user ID, and an opaque token based on the user's login token. The remote wiki does a callback to auth-api.php to get some user parameters, and then compares them with the eligibility requirements in the XML file. These parameters are the same as for local elections (generated by the same method in Auth.php).

How to run a board election

This is a collation of Andrew's experiences in running the election with two days' notice in 2011.

Voter eligibility

Since voter eligibility requirements are generally more complicated than is supported in vote XML dumps, we use "voter lists", stored in the securepoll_list table. Use the scripts in /a/common/php/extensions/SecurePoll/cli/wm-scripts to generate a voter list. Like any long-running batch job, you should do it in a screen on hume. For the "X edits over a short period, Y edits before date Z, across wikis" type of eligibility requirement, you can do the following:

  • Run populateBv20XXEditCount.php on all wikis to populate the edit count table in each database. You can expect it to take about three days. Copy one from previous years — don't forget to add the tables to all databases with a copy-modify of bv_20XX.sql.
  • Make sure the SecurePoll tables exist on all wikis with CentralAuth. I used this shell one-liner: $ for wiki in `</a/common/all.dblist`; do sql $wiki -e "show tables like 'securepoll_%'" | grep securepoll_lists | wc -l | xargs echo $wiki | egrep '\b0' >>~/wikis-missing-securepoll; done
  • When populateBv20XXEditCount.php has finished, run bv20XXVoterList.php across all wikis. This script will take about one day, and will create the voter list. Don't forget to change the name of the list that you're populating!

These scripts only handle the edit-count criteria. The session transfer parameters and the eligibility criteria in the XML file deal with blocked users, users blocked on multiple projects and bots. If somebody wants to make you add a new criterion, you have two options:

  • Add it as a condition to the voter list generation scripts. This is suitable for criteria that won't change between when you run the script and when the election is under way, such as edit counts as of certain dates.
  • Add it as an eligibility requirement to the election XML file. You may need to add parameters to the user information in Auth.php. This is suitable for criteria that need to be checked when a user tries to vote, such as blocks and group membership.

Exceptions

Exceptions that come from fixed lists, such as staff exceptions, should be put into the list manually on the appropriate wiki. For example, to allow Tim Starling to vote on mediawiki.org:

$ sql enwiki
mysql> INSERT INTO securepoll_lists (li_name,li_member) SELECT 'board-vote-2011', user_id FROM user WHERE user_name='Tim Starling';

Developer exceptions ARE DONE HOW (not code review anymore)?

Translations

A few gotchas, pitfalls and comments:

  • 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

Generating the XML files

Generally you will be creating two XML files. One is to be put on the vote.wikimedia.org wiki, and one is to be imported on all Wikimedia wikis to provide the stubbed interface to the session transfer (see the section about session transfers). The way I (Andrew) created these files is:

  • Run mwscript extensions/SecurePoll/cli/wm-scripts/bv2013/dumpMetaTranslations.php --wiki=metawiki, and saved the output to a file. Don't forget to modify dumpMetaTranslations.php to match this year's election! Of particular note:
    • You need to change the base ID. Figure out which base ID to use with this one-liner: for wiki in `</a/common/all.dblist`; do sql $wiki -e"select max(en_id) from securepoll_entity;" >>/tmp/max-entity-number; done; sort /tmp/max-entity-number | uniq. Get the highest number from the output, and round it up to the nearest ten or so.
    • You need to change the name of the election.
    • You need to adjust the list of election administrators.
    • You need to adjust the list of languages depending on which ones you want to offer the election in.
    • You need to adjust the start and end dates
    • You need to change the need-list parameter to accept the list you created (see #Voter eligibility).
    • You may need to tweak the other eligibility criteria.
  • I created one variant, labelled remote, which had the auth parameter set to remote-mw. This one went to SPI. Currently, dumpMetaTranslations.php generates this file.
  • I created another variant, labelled jump, which had the auth parameter set to local, and the jump-url and jump-id parameters set. The jump-url parameter is the URL of Special:SecurePoll on the remote wiki, the jump-id parameter will generally be the same as the election's base ID, and refers to the election ID in the remote variant of the XML configuration file.

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. Make sure that this election's base ID is different from the real election's base ID.

Email spam

There are scripts that can handle the mass-mailing are in the SecurePoll git repository. It is probably easiest to do a copy-and-modify for the newest elections. buildSpamTranslations.php pulls translations out of the database on meta, doSpam.php generates a list of emails to send, and sendMails.php actually sends the email. Don't forget to change:

Run a trial run by manually inputting your own details into sendMails.php and see what gets spat out. I'd recommend redirecting output for all of the scripts so you have a paper trail of exactly what happened in each step. Translations are pulled from a base page plus a language code. You might need to update buildSpamTranslations.php to strip more boilerplate from the pages. You should be prepared to deal with the following types of complaints:

  • Unflagged bots being invited to vote. Often the bot will not be flagged on all wikis that it is active — we need better handling for this, but you can find out which wiki a bot is being emailed from by grepping the output of doSpam.php.
  • Multiple accounts — usually people are understanding of the fact that the list is automatically generated, but some extra deduplication by email address would be helpful.
  • 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

  • We tended to get a lot of unflagged global bots receiving messages. Perhaps accounts bot-flagged on more than one wiki should be excluded altogether.
  • Some users with multiple accounts got multiple emails — deduplication by email address is an option worth considering.
  • We should send out the email reasonably early in the process — at most halfway through.
  • Bidirectional text tended to appear garbled in text-only emails. This was exacerbated by nonstandard formatting of the translation pages.
  • Translators need to be briefed about format — in particular, magic words don't work, and we need to try to keep boilerplate to a minimum so that we can extract the text reasonably seamlessly.
  • If we can get a machine readable list of accounts that have already voted, that might be useful for eliminating them from the mailout.

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. Duplicate removal is a large task, and as many trusted election administrators as possible should be encouraged to help with it. 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. 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 "dup" 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. 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. To generate each key:

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'

Key generation should be done by the 3rd party keeping the decryption key.

<property name="gpg-sign-key">...exported secret key...</property>
<property name="gpg-encrypt-key">...exported public key...</property>

Tallying

Board elections generally have too many records to allow tallying via the web interface. Instead, use the command-line interface. On the vote wiki side, export the election XML including votes:

mwscript extensions/SecurePoll/cli/dump.php --wiki=WIKI --votes <election name>

This XML file can then be distributed to the relevant election committee members, along with the private key for decryption. The private key can be obtained by the 3rd party holder using the key name from earlier:

gpg --armor --export-secret-keys 'Wikimedia Board Election 2011 encryption key'

The dump XML should be edited to contain the private key for decryption:

<property name="gpg-decrypt-key">...exported secret key...</property>

Then the file can be tallied using tally.php. The HTML output format is more suitable for pasting into a wiki page.

mwscript extensions/SecurePoll/cli/tally.php --wiki=WIKI --html board-election-2011.xml

The Schulze method tallier in SecurePoll has an incomplete implementation of Markus Schulze's tallying method, it does not include tie-breaking. This was due to constraints on software development time. In the event of the Schulze tallier declaring a tie, the votes should be further processed according to the method in Markus Schulze's paper in order to produce a result.

Creating vote dump

The election XML dump (from Tallying) can also be used to create an anonymized dump of votes. The script 'convertVotes.php' starts off the process by dumping the responses (along with the questions so that you know how to interpret) to a new file:

mwscript extensions/SecurePoll/cli/tally.php --wiki= electionvotedump.xml > convertedvotedump.txt

Becaues the votes are initially dumped in chonological order each question's votes should be shuffled. This can be done using shuf/gshuf or some other shuffling mechanism, makes sure to do so in a different file or in such a way as to not shuffle the part of the dump showing the question options.

Creating other kinds of elections

For documentation of the election properties which are set in the XML file, see the comment at the top of includes/entities/Election.php, and the comments on the ballot-specific module (e.g. includes/ballots/RadioRangeBallot.php).