Я уже рассказывал в прошлом году, что тогда нам так и не удалось независимо установить итоги голосования. Тогда это произошло из-за наличия в системе “переголосований”, для учёта которых использовался никому не подконтрольный второй блокчейн.
Что ж, похоже и на этот раз удача нас подвела. Несмотря на то, что переголосования на этот раз убрали, в этот раз нас настигла новая подстава: случайно сгенерированные ID кандидатов, уникальные для каждого из избирателей.
Что случилось?
Перед этими выборами мой коллега Лев Высоцкий написал плагин для браузера (https://github.com/vysotskylev/deg-plugin), который встраивается в код зашифровки транзакций для блокчейна и сохраняет промежуточные результаты: приватные ключи шифрования, ID выбранных кандидатов, а также результаты зашифровки. Параллельно с этим мы попросили людей сохранять HAR файлы (это запись сессии браузера) во время голосования.
Благодаря этому мы обнаружили, что ID кандидатов, которые шифруются в браузере во время голосования, отсутствуют в самом блокчейне, доступном сейчас на observer.mos.ru.
Мы заметили, что пользователь отдаёт голос за следующие ID кандидатов:
"voterChoices": [
1053875462,
339235495,
944863935,
306321285,
1032437978
],
При этом обнаружилось, что в самой выгрузке блокчейна с сайта такие ID никак не упоминаются. Начали разбираться и попросили двух избирателей с одного участка сохранить сессию браузера, чтобы найти, в чём проблема. Думали, набагали где-то в плагине. Но оказалось, что всё гораздо интереснее.
При голосовании браузер делает запрос на получение ID кандидатов в своём бюллетене. В ответ ему приходит JSON с маппингом ФИО кандидата -> deputyID. Далее при голосовании массив из выбранных избирателем deputyID шифруется и отправляется на сервер.
При сравнении сессий двух избирателей с одного участка обнаружилось вот такое (картинка из заголовка):
Сервер прислал разные deputyID для одних и тех же кандидатов. В предыдущие выборы эти ID кандидатов всегда совпадали с теми же ID, которые были указаны в блокчейне в момент создания голосования. И это не было проблемой.
Это должно значить следующее:
Чтобы правильно подсчитать голоса, где-то должен лежать маппинг из тех deputyID, которые выдали пользователю, в ID кандидатов в блокчейне.
Если где-то на сервере при выдаче бюллетеня также запоминается какие Deputy ID каким избирателям выдали, то это также нарушает тайну голосования (после расшифровки бюллетеней можно будет узнать кто как голосовал). Достоверно узнать, сохраняются ли такие данные, на данный момент невозможно, так как никаких исходных кодов в этом году опубликовано не было.
Я рассматриваю два варианта, где может храниться маппинг deputyID -> ID кандидата в блокчейне:
Худший вариант. Этот маппинг лежит на сервере и наблюдателям недоступен: минусы такого подхода очевидны. Если это так, то можно объявить более-менее какие-угодно результаты.
Вариант нормальный. Он записывается в саму транзакцию на приём бюллетеня в каком-то неочевидном виде, который мы не можем понять.
Первую гипотезу проверить невозможно, так как исходных кодов голосования у нас нет.
Поэтому давайте попробуем проверить вторую. Во время голосования есть возможность поставить галочку “хочу получить адрес транзакции в блокчейне”. Один из добровольцев поделился результатом своего голосования через плагин, а также поделился своим SID с голосования: 3a0ec864-b365-42b7-a048-ede80c4a1674. Согласно плагину, избиратель выбрал одного кандидата с DeputyID: 846578236.
Транзакцию с данным SID сейчас можно найти на observer.mos.ru (или в выгрузке дампа блокчейна):
Самым главным полем тут является неведомое поле `data`, смысл и содержимое которого на данный момент понять невозможно. Для этого нужны исходные коды системы голосования. Однако, возможно, там сохранился тот маппинг, который пришёл с сервера. Но даже если это и так, то очень вероятно, что он зашифрован каким-нибудь ещё неведомым ключом, чтобы мы независимо не подвели итоги (судя по опыту моего разбора ДЭГ в прошлом году).
Понять, что это за поле – так ни у кого до сих пор и не получилось. Можно только надеяться, что где-то там будет нужный нам маппинг deputyID -> ID кандидатов в блокчейне.
Что на это говорят в штабе наблюдения
После того, как мы выявили такие странности – в штаб по общественному наблюдению за ДЭГ отправился “десант” из программиста Ильи Снимщикова и двух кандидатов: Алисы Филюковой и Никиты Ильина.
Они обратились к Артёму Костырко (пиарщику московского ДЭГ) с просьбой опубликовать исходные коды. А также попросили разъяснить эту странность с ID кандидатов, уникальными для каждого из избирателей.
Про то, что нет исходного кода, были даны следующие обоснования:
Запретили публиковать контролирующие органы.
Дословно “у нас есть несколько прецедентов, когда на Гитхабе менялись файлы, выложенные туда”.
Не знаем куда выкладывать, если не на Гитхаб. Когда придумаем – выложим.
Когда кандидаты предложили записать код на флешку – им примерно ответили “вы код видели, это не doc файл, никому такого не рассказывайте”.
На мой взгляд, код таких систем должен быть публичен по умолчанию. А сами системы должны быть разработаны с учётом того, что их код будет опубликован. Такие системы нужно разрабатывать, имея в виду в первую очередь доверие граждан. Сейчас же мы имеем по сути чёрный ящик, ни код, ни даже обрабатываемые данные которого мы независимо проверить не можем.
Нам пообещали, что хотя бы после подведения итогов какие-то исходные коды будут опубликованы. Правда, я бы особенно не рассчитывал, на практике Артём Костырко много что обещал нашей рабочей группе, но так обещания и не сдержал. Но я буду рад, если всё-таки что-то выложат.
Про странности с разными ID кандидатов были даны такие объяснения:
Это защищает от “кликеров”, то есть вредоносов, которые по ID кандидатов что-то накликивают.
Что это не баг, а фича. Что по закону не должно быть возможности установить, как был учтён голос, без явного запроса от пользователя.
Про кликеры, конечно, странное объяснение. Во-первых, очевидно, что если ты хочешь написать кликер, то ФИО кандидата достаточно. Во-вторых, исходных кодов этой системы до сих пор нет, а хоть какая-нибудь внятная разработка любого кликера бы потребовала возможность тестировать его, что не представляется возможным, когда страницу с бюллетенем один человек может открыть всего лишь один раз.
Про то, что написано в законе я прокомментировать не могу, так как лучше оставлю это юристам. С технической точки зрения, транзакция с содержимым голоса не имеет никакого идентификатора пользователя и восстановить избирателя по ней нельзя. Более того, если система разработана так, что проверить свой голос невозможно без проставления явной галочки, то в такой системе можно удобно менять голоса. Если избиратель не поставил галочку о проверке своего голоса, то мы с некоторой вероятностью можем выбросить его бюллетень и вместо него записать в блокчейн другой, удобный уже организаторам. И с точки зрения статистики это будет не так просто установить.
Также в разговоре было сказано, что независимо подвести итоги голосования не должно быть возможно, так как этого требует закон. На это не могу реагировать никак, кроме как ¯\_(ツ)_/¯. Независимых итогов у нас не будет, значит, ничего не останется, кроме как доверять ДИТ Москвы.
Апдейт, практически breaking news
Во время тестового голосования транзакции на расшифровку голосов выглядели вот так:
{'method': 'decryptBallot',
'types': ['string', '(string,int64[],int64,int64,string,tuple)'],
'inputs': ['1',
['0f601bf8-12fd-4e1c-af18-c596c175ec71',
[111398230, 122538760, 132256288, 142737909, 153892268],
{'_hex': '0xd0'},
{'_hex': '0x0182da8d46ec'},
'04a7714575acd36eef7c36e4e7376f3c91e396531f471f492412d98ea83df70c',
['6277171106200de65b3a4e385675b23209dde1afbb1bcbbdfc2996b378292134e034d72b7f524d8e7326101282',
'2da3f6cd0483275bd8e711698d7b7cb22805c627b68faa77',
'a000701674a8fc9030058ec338cf6716c4117bfdb500d8b593fc4bb070e37d19']]],
'names': ['_votingId',
['_ballot',
['sid',
'decryptedValue',
'districtId',
'timestamp',
'txHash',
'encryptedData']]]}
При этом на реальном голосовании транзакции на расшифровку выглядят уже вот так вот:
{'method': 'decryptBallot',
'types': ['string', '(string,int64[],int64,int64)'],
'inputs': ['1',
['c52fdb20-3a21-4e6b-a2bf-412e467d3b45',
[213028079, 134021530, 153785398, 164166034, 122413774],
305,
1662917366958]],
'names': ['_votingId',
['_ballot', ['sid', 'decryptedValue', 'districtId', 'timestamp']]]}
Между тестовым голосованием и реальным убрали поле encryptedData. Это поле -- как раз тот оригинал транзакции, который приходит из браузера. Её можно было расшифровать и получить те deputyId, которые зашифровались в браузере.
Это значит, что мы имеем следующее:
Поле data которое непонятно что значит и непонятно как его расшифровать
Отсутствие оригинальной зашифрованной транзакции в расшифровке
Имея вот эти два пункта мы можем предположить такую схему фальсификаций:
Когда голосует какой-то избиратель, мы проверяем, поставил ли человек галочку "проверить мой голос"
Если нет, то можно безопасно подменять этот голос. Скажем, с вероятностью 50% подменяем голос избирателя.
Так как невозможно независимо установить, подменили ли голос избирателя -- наблюдатели не заметят подмены.