Что же не так с ДЭГ в Москве?


Последние три дня я занимался тем, что анализировал результаты ДЭГ в Москве по одномандатным округам в Госдуму. У меня есть некоторые результаты, которыми я бы хотел поделиться с общественностью. Однако основная цель этого поста — поделиться накопленными знаниями, чтобы кто-то ещё мог взглянуть на те данные, что лежат в блокчейне и перепроверить результаты.

Где лежат результаты и как их проверять


Часть исходного кода системы лежит в репозитории github.com/moscow-technologies/blockchain-voting_2021. Там лежит исходный код той части, которая про блокчейн, транзакции и тд, а также какой-то код фронтенда, написанный на JS. Отдельно отмечу некоторый забавный факт, что вместо общего репозитория со всем кодом там лежит несколько .tar.gz архивов, внутри которых уже есть код. В репозитории всего 4 коммита, сам код публиковали только 2 раза: 22 августа и 6 сентября. Никакой истории разработки у нас нет. Чтобы не приходилось распаковывать эти архивы — я залил распакованный репозиторий к себе на Github, можете читать код из браузера, если кому лень загружать и распаковывать самостоятельно: github.com/PeterZhizhin/blockchain-voting_2021_extracted

На сайте observer.mos.ru/all можно скачать дампы системы электронного голосования по одномандатным выборам, внутри которого будет лежать база с транзакциями, результаты расшифровки голосов и блоки в блокчейне. По кнопке «Скачать SQL дамп» загружается gz архив, внутри которого лежит один .sql файл.

Чтобы загрузить этот файл вам необходимо поднять у себя PostgreSQL базу. Я сделал это как-то вот так:

createdb -U postgres observer_20210920_090000
psql -U postgres -d observer_20210920_090000  -f <путь до распакованного SQL файла>

Процесс занимает достаточно много времени, зависит от скорости вашего диска, а итоговый размер базы данных будет около 15–17 гигабайт для одномандатных выборов в ГД для базы с сайта observer.mos.ru/all/servers/1/txs.

Внутри базы будет лежать три таблицы:

blocks, decrypted_ballots, transactions

.

Схема баз данных
CREATE TABLE public.blocks (
    height bigint NOT NULL,
    tx_count bigint DEFAULT 0,
    prev_hash character varying(64) NOT NULL,
    tx_hash character varying(64) NOT NULL,
    state_hash character varying(64) NOT NULL,
    error_hash character varying(64) NOT NULL,
    additional_headers json NOT NULL,
    precommits json NOT NULL,
    txs json NOT NULL,
    datetime timestamp with time zone NOT NULL
);
CREATE TABLE public.decrypted_ballots (
    store_tx_hash character varying(64) NOT NULL,
    decrypt_tx_hash character varying(64),
    decrypted_choice bigint[],
    status json NOT NULL
);
CREATE TABLE public.transactions (
    hash character varying(64) NOT NULL,
    message text NOT NULL,
    block_height bigint NOT NULL,
    position_in_block bigint NOT NULL,
    location_proof json NOT NULL,
    instance_id integer NOT NULL,
    method_id integer NOT NULL,
    status json NOT NULL,
    datetime timestamp with time zone NOT NULL,
    author character varying(64),
    payload json,
    signature character varying(128),
    internal_error text
);

Немного про каждую из баз данных:
База transactions содержит внутри себя все записи, которые включаются в блокчейн. Это основная база, с которой я работал. Транзакции бывают разных типов и отличаются полем method_id (число от 0 до 11 включительно). Вот все типы транзакций в порядке возрастания method_id, они указаны на сайте (https://observer.mos.ru/all/servers/1/txs), там же можно посмотреть примеры данных, которые лежат внутри транзакции, кликнув на любую из транзакций с данным типом в интерфейсе:

Внутри транзакции «Создание голосования» лежит маппинг ID кандидата в его имя, а также некоторые технические настройки голосования.

Внутри транзакций «Регистрация избирателей» лежат списки с уникальными ID голосующего — voter_id. Одна транзакция содержит в себе до 100 регистраций разных voter_id на данное голосование. Наверное, интересно тут ещё то, что для заданного voter_id в транзакции с его регистрацией не указан округ, куда он был непосредственно зарегистрирован. Информация об округе есть только в транзакциях на выдачу бюллетеня и на приём бюллетеня.

Внутри транзакции «Выдача бюллетеня» лежит упоминание о том, что данному voter_id был выдан (успешно или неуспешно) бюллетень. Пример payload такой транзакции:

{"voting_id":"ea067e1ad71565daff55627e4b35340620d53d644820478ee798e125efe657c2",
"voter_id":"66226133256418595367941344536751838140319057399092269074519877057970658693635",
"district_id":197,
"seed":"3877720680732874652"}

В данной транзакции мы видим, что заданному voter_id 66226133256418595367941344536751838140319057399092269074519877057970658693635 был выдан бюллетень на выборах по округу 197. voting_id — это ID всего процесса голосования, он один и тот же на все бюллетени.

Внутри транзакции «Приём бюллетеня» лежит один голос за конкретного кандидата. При этом сам голос зашифрован, для его расшифровки нужен приватный ключ, который публикуется после завершения голосования. Это сделано, чтобы до окончания дня голосования по зашифрованным голосам невозможно было понять, кто побеждает. Вот пример payload такой транзакции:

{
"voting_id":"ea067e1ad71565daff55627e4b35340620d53d644820478ee798e125efe657c2",
"district_id":199,
"encrypted_choice":{
  "encrypted_message":"32f384b35b17816d0b227e240dbdd0405b03902bab8b547eb2",
  "nonce":"5ca72f89ea94f8035e66e129cbad40e8f8154878b65e60af",
  "public_key":"dae3d8a3863eded175fec99b6a4c78dfbf8f340c3d1a1ba8d481c615bf912303"
}}

При этом отмечу, что внутри транзакции «Приём бюллетеня» лежит только сам голос, привязки к тому voter_id, который получил этот бюллетень, нет. Таким образом сохраняется тайна голосования. Непонятно, как проголосовал (по крайней мере по публичным данным) каждый из voter_id.

Транзакция «Публикация ключа расшифровки» публикует ключ голосования, который можно также найти в футере страницы под заголовком «ПРИВАТНЫЙ КЛЮЧ ГОЛОСОВАНИЯ». Ещё её можно найти в самих транзакциях:

select payload from transactions where method_id=8 limit 1;
{
"voting_id":"ea067e1ad71565daff55627e4b35340620d53d644820478ee798e125efe657c2",
"private_key":"54e3cf70f712b2ff727bde3849772fa811a9d5de796aa7d788d205aa86af04ad",
"seed":"14901105027823071500"
}

После публикации ключа расшифровки начинается сама расшифровка. Это транзакции с типом «Расшифровка бюллетеня», внутри которого лежит некий расшифрованный бюллетень. Связка между бюллетенем, полученным системой в транзакции «Приём бюллетеня», и расшифрованным бюллетенем проходит через базу decrypted_ballots. Нужно джойнить хэши store_tx_hash и decrypt_tx_hash с базой transactions. По полю store_tx_hash можно найти транзакцию «Приём бюллетеня», а по dectrypt_tx_hash можно проверить, как этот бюллетень был расшифрован.

В базе block содержится информация о блоках в блокчейне. Туда я особенно не смотрел. Вероятно, по этой базе можно проверить, что транзакции в базе transactions не модифицировались в ходе голосования. Но я этого не делал.

Мои претензии


Результаты голосования так и не были подведены системой (публичной системой)


Если вы откроете сайт наблюдения за ДЭГ по одномандатным выборам в ГД, то увидите, что там отсутствуют транзакции с типом «Завершение голосования» и «Завершение голосования с результатом». Более того, не все голоса были расшифрованы (по крайней мере публично). При этом ЦИК пишет (http://www.cikrf.ru/analog/ediny-den-golosovaniya-2021/p_itogi/), что учёл 100% бюллетеней, однако, по крайней мере публично, расшифровка голосов до конца не проводилась, видимо это было сделано как-то отдельно, непублично.

Если посмотреть, то транзакций с типом «Приём бюллетеня» в базе 2021969 штуки:

SELECT COUNT(*) FROM transactions WHERE method_id = 6;

А расшифровано всего 1319943 бюллетеней:

SELECT COUNT(*) FROM transactions WHERE method_id = 9;

Это даёт нам 702026 голосов (34%), которые публично расшифрованы не были. Дальше я покажу, как расшифровать оставшиеся голоса. Однако никакой сенсации в них нет, в нерасшифрованной части тоже не побеждает оппозиция.

Однако мне всё равно непонятно, какова причина того, что не все голоса были расшифрованы. У меня есть дамп SQL базы от 00:30 20 сентября 2021 по Московскому времени, она в точности совпадает с той выгрузкой, которая доступна сейчас на сайте. Последняя транзакция с типом «Расшифровка бюллетеня» была записана в 21:19 по Московскому времени:

SELECT MAX(datetime) FROM transactions WHERE method_id = 9;

После этого расшифровка голосов остановилась. Причина этого мне непонятна. После того, как расшифровка голосов остановилась — я поставил скрипт скачивать базу каждые 30 минут и проверять, может быть база была обновлена. Однако по сей момент времени отличий от той базы, которая была в 00:30 20 сентября 2021 года и тем, что есть сейчас — нет. Более ранних дампов у меня нет.

Из-за этого некоторые избиратели, которые воспользовались инструкцией Голоса (https://www.golosinfo.org/articles/145370), не смогли проверить, как их голос был расшифрован в системе. Мне написал избиратель с хэшом 60b7b467f454fc80e41ac90008b03220aded815b41d619011d4a16155f3f3a18. Как можно заметить, его голос расшифрован не был.

Релевантный код, который расшифровывает голоса, можно найти вот тут: github.com/PeterZhizhin/blockchain-voting_2021_extracted/blob/19aa79ef3a0388f872543dc3de2913d7a7b067b5/blockchain/dit-blockchain-source/services/votings-service/src/schema/ballots_storage.rs#L447-L520

На основании этого кода я написал утилиту, которой можно расшифровать те голоса, которые не были расшифрованы системой: github.com/PeterZhizhin/moscow_deg_decode_votes. Независимо от меня такую же утилиту написали на Питоне: gist.github.com/SaveTheRbtz/246eab8557b0217ab3945e15cef6ffe8#file-decode-py

Все расшифрованные голоса по одномандатным выборам в Москве можно найти вот тут (формат хэш записи транзакции «Приём бюллетеня» → ID проголосованного кандидата): drive.google.com/file/d/1rVu4yGS1WBhJnVTnJlygmqJvodj83mZ3/view? usp=sharing
Расшифровка всех голосов в 24 потока у меня заняла где-то 30 минут (без учёта поиска и написания релевантного кода для расшифровки). Это при том, что код мой для этого совсем не оптимален (простите за качество кода, это всё в очень авральном режиме писалось). Есть ещё альтернативный код, который не использует subprocess.run (https://gist.github.com/SaveTheRbtz/246eab8557b0217ab3945e15cef6ffe8#file-zbatchdecrypt-ipynb), но я писал как додумался за имеющееся время: я нашёл кусок кода в системе ДЭГ, который отвечает за расшифровку, и перенёс его в отдельную программу.

Мой код вот тут
import subprocess
from joblib import Parallel, delayed
import tqdm
import pandas as pd
from sqlalchemy import create_engine

engine = create_engine('postgresql+psycopg2://postgres:<пароль от базы>@localhost/observer_20210920_090000')
conn = engine.connect()

def decode_vote(private_key, public_key, nonce, encrypted_result):
    return subprocess.run(['simple_decode_code.exe', private_key, public_key, nonce, encrypted_result], capture_output=True).stdout.decode('utf-8').strip().split(';')

def decode_vote_with_hash(h, *args):
    return {'tx_store_hash': h, 'decoded_vote': decode_vote(*args)}

PRIVATE_KEY = '54e3cf70f712b2ff727bde3849772fa811a9d5de796aa7d788d205aa86af04ad'


all_encrypted_ballots = pd.read_sql_query("""
SELECT
hash,
payload->>'district_id' as district_id,
payload->'encrypted_choice'->>'encrypted_message' as encrypted_message,
payload->'encrypted_choice'->>'nonce' as nonce,
payload->'encrypted_choice'->>'public_key' as public_key,
datetime
FROM
public.transactions
WHERE method_id = 6
""", con=conn)


decoded_votes_evaluated = Parallel(n_jobs=-1, backend='threading')(delayed(decode_vote_with_hash)(x.hash, PRIVATE_KEY, x.public_key, x.nonce, x.encrypted_message) for i, x in tqdm.tqdm(all_encrypted_ballots.iterrows(), total=all_encrypted_ballots.shape[0]))

Если кто-то сталкивался с проблемой, что вы не нашли свой расшифрованный голос в системе, то можете воспользоваться утилитой. Я сверил те 1.3 млн голосов, которые были расшифрованы публично с моей расшифровкой — результат расшифрования совпадает.

Подсчёт выборов МГД


Тут я немного отвлекусь от одномандатных выборов в Москве и хочу обратить внимание на те выборы, где системой результаты всё-таки были подведены. Давайте посмотрим на выборы депутатов МГД через ДЭГ. Мы увидим что там результаты подведены всё-таки были. Вот транзакция с типом «Завершение голосования с результатом»: observer.mos.ru/all/servers/3/txs? methodId=11. Давайте посмотрим на код, как же появляются результаты этой транзакции. Публичный API в виде протобуфа: github.com/PeterZhizhin/blockchain-voting_2021_extracted/blob/main/blockchain/dit-blockchain-source/services/votings-service/src/proto/transactions.proto#L102-L106. Код обработки данной транзакции: github.com/PeterZhizhin/blockchain-voting_2021_extracted/blob/main/blockchain/dit-blockchain-source/services/votings-service/src/transactions/finalize_voting_with_results.rs#L66-L115. По коду видно, что данный обработчик принимает на вход заданные результаты выборов и просто публикует их, не производя при этом подсчёт. В finalize_voting.rs есть вызов функции ballots_storage.tally_results (функция подсчёта голосов), а в finalize_voting_with_results.rs только ballots_storage.publish_results (публикация заданных результатов).

При этом результаты по МГД попытались подвести 3 раза, 2 из них оказались неуспешными. Видимо, забыли опубликовать транзакцию с окончанием голосования, о чём можно судить по сообщению об ошибке. Почему это произошло — мне непонятно.

За подсчёт должна отвечать транзакция «Завершение голосования» (код github.com/PeterZhizhin/blockchain-voting_2021_extracted/blob/main/blockchain/dit-blockchain-source/services/votings-service/src/transactions/finalize_voting.rs), она производит подсчёт. Однако она в транзакциях отсутствует, поэтому я могу с уверенностью сказать, что система (по крайней мере публичная, на исходной код которой я могу посмотреть) результаты выборов не считала.

Более того, она и не могла посчитать выборы из-за переголосований, о чём будет ниже.

Как же подводили итоги в ГАС Выборы?


Подробнее про то, какие расхождения в протоколе ГАС Выборы я нахожу, и откуда они могут браться, будет написано ниже. Тут я хочу скорее обратить внимание на то, каким именно образом был утверждён протокол. Публичного подсчёта, по факту, так до сих пор и не было. Но протокол откуда-то всё равно должен был взяться?

По сообщениям наблюдателей за ДЭГ, членам комиссии протокол просто откуда-то спустили, который им достаточно было подписать (комментарий достаточно эмоциональный, но я считаю, что конструктивный):

Как работает УИК ДЭГ. Им принесли откуда-то распечатанные протоколы, они пустили их по кругу на подпись. Проголосовали за утверждение. Вуаля. Комиссия легализовала результаты электронного голосования, которые никто из них не проверял и не участвовал в подсчёте результатов. Но единогласно у членов комиссии нет оснований не доверять.

t.me/shendernews/1886

У меня не было бы к нему претензий, если было бы возможно публично установить, что результаты с протоколом сходятся. Однако этого сделать на данный момент невозможно.

Более того, протокол ДЭГ ещё и пришлось потом переделывать. Так как, по сообщениям наблюдателей:

Не сошлись цифры итоговые, когда их стали вбивать в ГАС Выборы. Потому что протоколы составляли… руками в табличке Эксель, а не как мы все думали — автоматической выгрузкой результатов из блокчейна

t.me/shendernews/1951

Количество избирателей, включенных в ДЭГ


Проблема с транзакцией «Регистрация избирателей»


Как я писал выше, в базе данных с транзакциями есть тип транзакций «Регистрация избирателей». Внутри одной транзакции находится до 100 voter_id избирателей, которым потом выдаются бюллетени. Однако замечу, что внутри данной транзакции невозможно узнать, к какому округу приписан избиратель.

В протоколе ГАС Выборы по каждому округу при этом эти цифры есть. К каждому из ОИК был приписан «виртуальный» УИК ДЭГ с номером №50xx, в котором есть результаты отдельно по ДЭГ на каждом из округов. Так, например, согласно протоколу ГАС Выборы в 198 округе (виртуальный УИК №5003) было в списки включено 137036 избирателей, строчка «Число избирателей, внесенных в список избирателей на момент окончания голосования»

Скрытый текст

(привет тому, кто придумал эту фигню со скремблером на сайте ГАС Выборы)

: www.vybory.izbirkom.ru/region/izbirkom? action=show&root=774050015&tvd=4774050194611&vrn=100100225883172®ion=77&global=&sub_region=77&prver=0&pronetvd=null&vibid=4774050194611&type=463

Однако в транзакции «Регистрация избирателей» не указан округ избирателя. Таким образом, мы не можем проверить, что бюллетени, которая выдаёт система, действительно выдаются по тому округу, к которому они приписаны. По крайней мере, в коде у меня про это найти ничего не удалось. Но буду рад, если кто-то подскажет, вдруг я не нашёл, где реализована эта логика.

Мы можем посчитать только общую сумму по избирателям, которые были включены на ДЭГ по одномандатным выборам: 2014765 избиратель.

SELECT SUM(voters_in_transaction) FROM
(SELECT json_array_length(payload->'voters') as voters_in_transaction FROM transactions WHERE method_id = 1) as t;


Несостыковки с количеством заявок на mos.ru и включенными в ГАС Выборы


На сайте mos.ru есть сервис проверки включения избирателей в систему ДЭГ: www.mos.ru/proverka-deg

Я скачал данные об одобренных заявлениях, чтобы проверить, как согласуется количество поданных заявок на mos.ru и количество избирателей, включенных в списки ДЭГ по данному округу (с одобренным статусом заявки). Количество избирателей, включённых в ДЭГ по данному округу, всегда больше, чем заявок на mos.ru (сырые данные можно найти вот тут docs.google.com/spreadsheets/d/1gVHMrxHurtBFWbYUOXKVjYHxsv15SD1CYcw5g37tZCA/edit? usp=sharing)

bvzr8t23h3dbookkfilbdto6sya.jpeg

На сайте указано, что через сайт gosuslugi.ru также было подано дополнительно 455230 заявлений. Однако мне не удалось узнать, сколько из этих заявлений было одобрено, и не получилось узнать, как эти заявления распределились по округам. Как и проверить, реальные ли люди подавали эти заявки (впрочем, тут и заявки с mos.ru сложно проверить).

Так, в 198 округе в списки избирателей на ДЭГ было включено 137036 человек, а одобренных заявок через mos.ru было всего 111959, что даёт 25077 человек, которые записались не через mos.ru. По всей видимости, они должны были придти через Госуслуги, но так как аналогичная выгрузка по Госуслугам отсутствует: мы не можем точно проверить, соответствует ли количество одобренных заявок на ДЭГ и количество избирателей, включенных в ДЭГ по данному округу.

В теории, чтобы проверить, правда ли все заявки на ДЭГ настоящие: надо посчитать по округам количество избирателей, которые были исключены из списка избирателей по «бумажным» УИК из-за ДЭГ, эта информация должна быть в книгах избирателей на УИКах. Сумма должна сойтись с количеством избирателей, включенными в ДЭГ по округу. Из той информации, что мне доступна — этого наблюдателями за «бумажным» голосованием не делалось. Но если у кого-то есть, то давайте сравнивать.

С учётом этого мне не удаётся однозначно сказать, что все заявки на ДЭГ, которые есть в блокчейне, пришли от настоящих людей.

Невозможно понять, какие из бюллетеней переголосованные


Перейдём, наверное, к самой главной проблеме этого электронного голосования. Именно по этой причине процесс подведения итогов оказался непубличным и, на данный момент, абсолютно не перепроверяемым.

На выборах ДЭГ в Москве была введена функция повторного голосования. Я предпринял попытки понять, каким именно образом нужно учитывать переголосования.

Я нашёл человека, который проголосовал два раза на выборах по одномандатному округу и два раза сохранил хэши голосования по инструкции от Голоса.

twitter.com/f8f82/status/1439978015698587663

При этом, если открыть эти две транзакции в системе наблюдения за ДЭГ, то между ними не найдётся ничего общего:
observer.mos.ru/all/servers/1/txs? query=91f3b76042cfc23a18210fc4bf3b5d679a5b9740b35c90f27550566374e965b2&methodId=6
observer.mos.ru/all/servers/1/txs? query=8627f07a2c50fd33bd683cd5672a97655ffb023fcfd208aa6cd93694771f2e94&methodId=6

Мы не видим никакого флага или идентификатора, который бы помог нам установить, что эти бюллетени пришли от одного человека. На основании какой-либо публичной информации, кажется, никому ещё не удалось это сделать.

Судя по интервью Артёма Костырко на Эхо Москвы это сделать, похоже, и невозможно. Нужна какая-то дополнительная информация, которую обещали выложить позже. Я пока не понимаю, каким именно мы можем проверить достоверность той информации, которая будет выложена ДИТ Москвы. Так я и не понимаю, почему информация, необходимая для подсчёта голосов, появляется уже после подведения итогов.

Я попытался поискать по коду, на что влияет флаг revote_enabled, но так и не понял, как учитывать переголосования.

Нужен бы был какой-то ID, который бы по данному бюллетеню указывал, что это переголосование. Я не знаю, как вообще, в теории, можно реализовывать переголосования, если у вас нет базы ID голосующего → ID всех бюллетеней этого человека.

Судя по интервью Артёма Костырко, для этого используется какой-то алгоритм с квадратичной сложностью от количества переголосованных бюллетеней. А при их большом количестве это занимает много времени, из-за чего, по словам Артёма Костырко, подсчёт и занял так много времени. Детали этого алгоритма раскрыты не были, в опубликованном исходном коде мне не удалось найти ничего про подсчёт с учётом переголосования. Собственно, поэтому подсчёт и нельзя назвать публичным, он производился по какому-то иному, не опубликованному алгоритму.

Масштаб проблемы: голосов в системе ДЭГ по каждому округу больше, чем было выдано бюллетеней

Давайте посчитаем по каждому из округов, сколько там было выдано бюллетеней (транзакций с типом «Выдача бюллетеня»), и сколько голосов оказалось «в урнах» ДЭГ (транзакций с типом «Приём бюллетеня»).

Общий подсчёт по всем округам доступен прямо на сайте observer.mos.ru/all/servers/1/txs:

104% конверсия: выдано бюллетеней 1 ‍943 ‍590, а принято бюллетеней 2 ‍021 ‍969.

Давайте поймём, откуда берутся эти цифры. Цифра 1 943 ‍590 — это количество успешных транзакций с типом «Выдача бюллетеня»:

SELECT
COUNT(*) as cnt
FROM public.transactions WHERE method_id = 4 and status->>'type' = 'success';

Цифра 2 ‍021 ‍969 — это количество успешных транзакций с типом «Приём бюллетеня»:

SELECT COUNT(*) FROM transactions WHERE method_id = 6 and status->>'type' = 'success';

Давайте теперь посмотрим на распределения по округам. Сколько лишних бюллетеней попало в каждом из округов (сырые данные есть вот тут: docs.google.com/spreadsheets/d/1gVHMrxHurtBFWbYUOXKVjYHxsv15SD1CYcw5g37tZCA/edit? usp=sharing):

bobjfv20qpxmcd7hvjrdcgfvds4.jpeg

Так, по округу 198 было системой выдан 131 930 бюллетень. А в «урнах» оказалось уже 133 283 голоса. Это означает, что как минимум 1 353 бюллетеня в системе в 198 округе лишние, в других округах этот разрыв ещё больше. При этом надо понимать, что некоторые избиратели имели право получить бюллетень на ДЭГ, но его не использовать. Поэтому реальная цифра корректных «бюллетеней» должна быть меньше, чем количество выданных бюллетеней.

Каждый избыточный бюллетень — это те самые переголосования. Они должны быть учтены в протоколе ГАС Выборы. Давайте посмотрим, сколько голосов было «выкинуто» из-за переголосований в каждом из округов (разница между «Число действительных избирательных бюллетеней» и количеством бюллетеней в «урнах» ДЭГ).

19hkrsbxbu7vorjqoecekepiymg.jpeg

Например, согласно протоколу ГАС Выборы, в 198 округе «Число действительных избирательных бюллетеней» — 120 207. То есть какие-то 13 076 бюллетеня при подсчёте учтены не были. Какие именно это были бюллетени — мы не узнаем до тех пор, пока ДИТ не опубликует, как же нам по записям в списке транзакций понять, какие именно из голосов были переголосованы, и какие именно голоса надо учитывать.

Как меняются проценты за кандидатов после учёта «переголосовавших» в ГАС Выборы


Последнее, что я сделал с этими данными — это посчитал проценты за каждого из кандидатов, если подсчитать все голоса в системе блокчейна. При этом, так как мы не можем никак учесть правильно переголосования, будем считать каждый «избыточный» бюллетень за действительный. И как эти проценты соотносятся с тем, что указано в ГАС Выборы (где результаты должны быть учтены уже с исключением переголосований).

Функция переголосования рекламировалась как функция защиты от административного давления. Например, когда тебя заставили проголосовать на работе за кандидата, который тебе не нравится, но потом ты переголосовал за нужного тебе кандидата. Если эта функция и правда использовалась так, то мы бы ожидали, что от корректного учёта переголосований процент административного кандидата стал бы в округе ниже, чем если бы мы учитывали все бюллетени (скорее выбрасываются голоса за административного кандидата, чем за оппозиционного).

Для этого я использовал свой расшифровщик всех голосов и сравнил проценты за каждого из кандидата, если пользоваться результатами из транзакций и не учитывать переголосования. И процентами в ГАС Выборы за каждого из кандидатов.

Проценты есть все вот тут: docs.google.com/spreadsheets/d/1L1SUilxRVWXLn3YOPmOWAfTlcv08NCP6KvexwxaEKk4/edit? usp=sharing

По факту получается всё ровно наоборот. Если сравнить результаты, когда переголосования считаются как реальные голоса, и результаты в ГАС Выборы (где из переголосований должны быть корректно учтён только последний голос), то процент административного кандидата не очень сильно, но растёт во всех округах (примерно на 1%).

Если мы считаем, что переголосование использовалась именно как защита от административного давления («сначала голосую за административного кандидата, потом за оппозиционного»), то публичные данные показывают, что в результатах в ГАС Выборах по ДЭГ что-то не так. Однако надо сказать, что таким результатам в ГАС Выборах может быть и другое объяснение: например, что оппозиционные избиратели несколько раз переголосовывали за того же самого оппозиционного кандидата, а избиратели административных кандидатов этого не делали.

Выводы


Я хотел обратить внимание на те странности, которые сделали систему ДЭГ непрозрачной на этих выборах:

  • Результаты выборов ДЭГ никакими публичными программами установлены не были. На одномандатных выборах в ГД подсчёт системой просто не производился, а на выборах в МГД в систему вместо подсчёта голосов просто загрузили итоговый протокол. Подсчёт голосов из-за системы переголосований проводился в каких-то других системах, не подконтрольных наблюдению, исходного кода которых мы найти не можем. В публичных системах недостаточно данных для подведения итогов.
  • Мы не можем проверить заявки на ДЭГ и количество людей, включенных на ДЭГ в конкретном округе. Заявок на ДЭГ, поданных через mos.ru, сильно меньше, чем избирателей, включенных на ДЭГ в этом округе. По заявлениям с Госуслуг нет такой же аналитики, как на mos.ru, и невозможно проверить, что все недостающие избиратели, включенные на ДЭГ не через mos.ru, действительно пришли из Госуслуг.
  • Мы не можем по публичным данным отследить, какой голос был переголосовавший, а какой — единственный для данного человека. Это делает подсчёт голосов по публичным данным невозможным.
  • Масштаб переголосований по каждому округу в Москве был очень серьёзный. Количество бюллетеней, принятых системой ЭГ, всегда оказывалось больше, чем она выдала в каждом округе.
  • В каждом из округов, в системе ГАС Выборы не была учтена значительная часть бюллетеней, которые приняла в себя система ДЭГ. Из-за непрозрачности системы подсчёта мы не можем достоверно узнать, какие именно бюллетени были учтены системой как недействительные.
  • Если предположить, что переголосования в системе ДЭГ действительно используются как защита от давления, то процент оппозиционных кандидатов должен был вырасти. На практике в ГАС Выборы от учёта переголосовавших процент административного кандидата тоже вырос.


Что ещё надо сделать


  1. Требовать публичного подсчёта голосов в ДЭГ. Подсчёт голосов в ДЭГ был сделан какими-то непубличными системами, надо выносить их в паблик.
  2. Надо подождать, что нам выдадут для подсчёта переголосовавших бюллетеней. Исходя из этого можно продолжить анализ: надо посмотреть, какие именно бюллетени переголосовывались и как.
  3. Я вообще не касался того, что связано с блокчейном. Надо проверить, действительно ли все транзакции корректны.
  4. Надо сравнить выгрузку транзакций до подведения итогов и после публикаци ключа голосования. Старые транзакции не должны быть изменены. Мне отправляли дампы SQL до окончания голосования, надо их сравнить, правда ли, что данные только добавлялись, но старые не изменялись.


На будущее


  1. Надо просить наблюдателей на «бумажных» УИКах считать количество избирателей, исключенных из-за ДЭГ. Ну или чтобы в протокол по УИК добавили строчку, с количеством откреплённых избирателей по ДЭГ. Число таких избирателей по всем УИКам в округе должно сходиться с общим количеством избирателей, включенных в ДЭГ.
  2. Достаточно сложно было найти людей, которые сохраняли хэши своих транзакций во время голосования. Ещё сложнее было найти людей, которые бы переголосовывали и записывали хэш голосования. Надо научить сторонников, которые пользуются ДЭГ записывать хэши голосований, чтобы у нас потом было с чем сравнивать.
  3. Было трудно найти базы с транзакциями за моменты до подведения итогов голосования. Наблюдателям ПСГ/ПРГ на УИК ДЭГ предлагаю в будущем сохранять все промежуточные выгрузки, чтобы смотреть, что никакие из транзакций не были подменены.
  4. В системе ДЭГ очень не хватает большей грануляции по УИК. Мы не можем узнать, как в данном конкретном районе результаты на «бумажном» УИК отличаются от «электронного» голосования в этом районе — есть только общие цифры по всему округу.

По горячим следам объявленного «пересчёта ДЭГ»


Как я постарался объяснить в этом посте, основная моя претензия к системе ДЭГ — это то, что итоги голосования были подведены, похоже, не той системой, исходной код которой у нас есть (потому что опубликованная система не умеет учитывать переголосования), а какой-то другой системой, скрытой от нашего наблюдения. Было обещано выложить исходные коды системы подведения итогов: обязательно посмотрим, что это за код.

Я бы хотел ещё раз отметить, что в списке транзакций не указано, какие именно бюллетени были переголосованы, и непонятно, как список переголосованных бюллетеней можно было бы получить. Я хочу, чтобы ДИТ Москвы опубликовал алгоритм, который бы позволил получить список переголосованных транзакций «Приём бюллетеня», используя только ту выгрузку, которая доступна сейчас публично на сайте observer.mos.ru/all. Если же для учёта переголосованных бюллетеней нужна какая-то дополнительная информация, то я бы хотел, чтобы её опубликовали и доказали, что её невозможно подделать.

Будет очень нехорошо, если ДИТ просто опубликует базу «вот эти транзкции Приём бюллетеня мы считаем переголосованными вот этой транзакцией Приём бюллетеня» без каких-либо доказательств, что она настоящая.

© Habrahabr.ru