Technical difficulties Possibly by accident, Moscow officials released the decryption key for the city's online votes. We put it to use and found some weird stuff.
In three of Moscow’s voting districts, the city’s September 8 legislative elections also served as a test for a new online voting system. In one of those districts, the online vote proved decisive: While independent candidate Roman Yuneman won the most paper ballots in District 30, he lost to pro-regime candidate Margarita Rusetskaya thanks to the latter’s electronic results. Moscow City Hall published the results of the city’s online voting but did not provide access to the raw voting data behind those results. We found the key to that data, decrypted all of Moscow’s online votes, and reconstructed the three races that used online voting down to the minute.
Moscow City Hall published the election’s online votes as encrypted data…
Every half hour during the online voting period, Moscow’s Information Technology Department posted aggregated, anonymized voting data, including encrypted votes, on a special website. For each vote, that data identified the following information:
- The voter’s district (1, 10, or 30)
- The block within the system’s blockchain where the transaction representing the vote was recorded
- The time at which that block was formed
- The encrypted vote
... and (accidentally?) enabled public access to the secret key needed to decrypt the data
Once voting had concluded and a final CSV file containing data about 9,810 votes had been published, Moscow officials reconstructed a private key to decrypt those votes. The key had to be pieced together from fragments that had been entrusted to seven different officials in advance. Unexpectedly, the reconstructed key was entered into the voting experiment’s public blockchain. Meduza was able to find the block that contained it and the transaction itself through the web interface for online voting observers before access to the blockchain was shut down.
We don’t know whether this was an inadvertent mistake or not. However, if the officials running the online voting experiment had intended to give observers access to the private key, it’s not clear why they shut down access to the blockchain’s web interface a few hours later.
We were able to decrypt every online vote
To decrypt the transactions in the blockchain, which had been encrypted using the ElGamal system, we needed more than the private key. We also had to find one component of the public key that had been used to encrypt the data in the first place: its modulo.
To find the public key’s modulo, we turned to our science editor, Alexander Ershov. He had voted online on September 8, and he agreed to give us a look at the HTML page for his ballot. That page contained the public key and the modulo we needed.
If you try to decrypt the votes yourself, remember that every candidate in the online election was assigned a number. Just before the encryption process took place in any given user’s browser, that number was squared, meaning that you’ll have to take the square root of the decrypted figure to find the original candidate number.
We even figured out which way our coworker voted
Our science editor, Alexander Ershov, recorded the online voting process in a HAR file. That file type allows users to save all incoming and outgoing traffic as they visit a web site or use a web app. Within that archive, we found Alexander’s encrypted vote. From there, figuring out who he voted for was a piece of cake.
Recording your own online voting isn’t a complicated process at all. You don’t even have to install any special programs to do it. Browsers like Google Chrome, Mozilla Firefox, Safari, and even Microsoft Edge have built-in tools to record web traffic in HAR files.
If election officials knew in advance that they were going to publish the private key to the blockchain temporarily, then in theory, other government officials who forced their subordinates to vote a certain way could have used that opportunity to determine how their employees really voted.
The city’s online voting data looks pretty weird...
The election commissions for Precincts Number 5001, 5002, and 5003 all made their election results public, and those results match up perfectly with the decrypted voting data we obtained. That said, without direct access to the voting blockchain, we can’t determine how many electronic ballots were initially distributed to voters.
However, you don’t even have to decrypt anything to notice that something was off in these races. All you have to do is split the voting period into five-minute chunks and group together the online voting blocks that were recorded during each of those chunks.
The rate of incoming votes during the 12-hour voting period was uneven. There was a stretch of three hours during which almost no votes at all were added to the blockchain, and half of all the blocks recorded were added during a single hour (though each of those blocks contained only 1 – 3 votes). In the middle of the voting period, there was even a five-minute stretch when almost 1,400 votes were added to the blockchain. That’s about 14 percent of the total number of votes.
- Between 08:02:58 and 09:26:04, 53 blocks (#668 – #2046) were formed. They contained 3,308 votes. The pause between blocks or groups of blocks was around two minutes.
- A technical failure took place. It lasted 54 minutes.
- At 10:20:12, one block (#2525) was formed. It contains 35 votes.
- A second technical failure took place. It lasted 48 minutes.
- From 11:08:22 to 11:19:08, seven blocks were added to the blockchain (#2651 – #2818). They contained 92 votes, and there were pauses of two minutes between successive blocks.
- A break in voting was announced. It lasted one hour and 10 minutes.
- From 12:29:18 to 13:08:18, 25 blocks (#2956 – #3516) were recorded. They contained 1,189 votes, and there were pauses of two minutes between successive blocks.
- From 13:10:28 to 14:13:46, 328 blocks (#3541 – #4686) were formed. They contained 612 votes. Pauses between blocks were a few seconds long, and pauses between groups of blocks lasted for between two and 11 minutes.
- From 14:16:20 to 14:20:44, 31 blocks were recorded (#4731 – #4804). They contained 1,362 votes. Pauses were on the order of dozens of seconds.
- From 14:21:02 to 15:22:48, 76 blocks were formed (#4809 – #5530). They contained 1,092 votes. Pauses lasted around one minute.
- From 15:24:48 to 20:00:44, 142 blocks were formed (#5544 – #7168). They contained 2,119 votes. Pauses again reached around two minutes.
- At 20:15:28, a final block (#7190) containing a single vote was recorded.
But it looks like that was because of technical problems, not vote dumping
Here’s where we do need those decrypted votes. Let’s look at the distribution of votes among three competitors in a single district: The independent candidate Roman Yuneman, the pro-regime candidate Margarita Rusetskaya, and Vladislav Zhukovsky, a Communist Party candidate who was listed in opposition politician Alexey Navalny’s Smart Vote program. The bursts of votes did not favor any single candidate to a degree that looks suspicious on its face.
What is odd was the distribution of pro-regime votes over time
In all three districts that used online voting, voters who chose pro-regime candidates submitted their ballots in the morning (that is, in the hour and a half before the first major technical glitch) at noticeably higher rates than voters who chose independent candidates.
We don’t know whether or not this data could make it possible to identify the scale and nature of any forced voting in these races.
We reconstructed the online voting process down to the minute
This was possible because a blockchain was used to record votes. We know for sure that every vote recorded in a given block could only have been cast before the moment that block was added to the blockchain. The developers who created Moscow’s online voting system said it would generate random pauses between the completion of every ballot and the time that ballot was added to the blockchain. However, it’s possible that those pauses only tend to shuffle votes within individual blocks: Alexander Ershov’s vote landed in the very first block that was formed after he submitted his ballot.
To get a sense of how precisely Moscow’s online voting can be reconstructed, take a look at the contest between Roman Yuneman and Margarita Rusetskaya. The video below tracks the difference between the two candidates’ vote totals by taking the final results of paper voting as a given from the start and then progressively adding online ballots as they were recorded over the 12-hour voting period. The result shows how Yuneman’s 581-vote lead over Rusetskaya in paper voting gradually crumbles into an overall 84-vote loss due to online voting.
We’re sharing all this data for other researchers to use
You can download:
- The public and private keys that were used in Moscow’s online voting on September 8, 2019
- All the encrypted ballots
- All the decrypted ballots
Don’t forget to write to us if you find anything interesting in this data.
Update: Following the publication of this report in Russian, Moscow IT Department Deputy Chair Artyom Kostyrko told the state-owned wire service TASS that the private key was published intentionally as a necessary step toward decrypting the voting blockchain. No government official has yet explained why the key was temporarily made public and subsequently taken down.
Translation by Hilah Kohen
Meduza survived 2024 thanks to its readers!
Let’s stick together for 2025.
The world is at a crossroads today, and quality journalism will help shape the decades to come. The real stories must be told at any cost. Please support Meduza by signing up for a recurring donation.