ИсследованиеПолитика

Пошли на крайние химеры

Как в блокчейне скрестить голос за оппозицию с вбросом за власть? Результаты эксперимента программиста Петра Жижина

Фото: РИА Новости

Фото: РИА Новости

На дальних подступах и непосредственно перед выборами авторы «Новой газеты» предупреждали о потенциальных уязвимостях дистанционного электронного голосования (ДЭГ). В день выборов на электронном избиркоме происходили странные события, о чем нашим читателям рассказали его свидетели — Илья Сухоруков, Анна Лобонок и Николай Колосов. Мы попытались получить внятные объяснения у ЦИК, департамента информационных технологий (ДИТ) Москвы и отвечающего за электронное голосование в Москве Артема Костырко, но от личного разговора Артем Костырко и Элла Памфилова уклонились, а в письменной коммуникации мы не получили внятных ответов.

Статистический анализ физика Максима Гонгальского показал, что, скорее всего, основным механизмом фальсификаций, обеспечившим победу провластных кандидатов, было «скручивание» голосов оппозиции через переголосование, и оценил количество «скрученного» в 250 000 голосов. Отчет технической группы ДЭГ такую версию не опроверг.

Программист Петр Жижин в своей статье на портале для программистов, которую он популярно изложил нашим читателям в интервью Юлии Латыниной, установил существование «секретного блокчейна». Это исключает проверяемость результатов выборов по публичному блокчейну, опубликованному ДИТ Москвы. Петр Жижин тогда не смог дать ответа: как в системе могли осуществляться вбросы? А теперь такой ответ у него есть.

Если очень коротко:

сначала в публичный блокчейн могли вписываться голоса за провластных кандидатов от «не людей». А после завершения голосования в «приватный блокчейн» могли вписываться «химерические» строчки,

в которых поле, соответствующее избирателю, было бы взято от живых людей из других строк приватного блокчейна, а поле, соответствующее голосованию за какого-либо кандидата — взято от вброшенного «не человеком» бюллетеня за власть из публичного блокчейна.

Кроме того, было выяснено, что опубликованный ДИТ Москвы код не соответствовал тому, который запускался на выборах: для выборов увеличили время, в течение которого доступно переголосование. Оказалось также, что во время работы программы осуществлялась запись не предусмотренной законом и порядком проведения ДЭГ простой таблицы с данными избирателей, сведения о которой отсутствуют в документации на ДЭГ.

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

Мы первыми публикуем результаты дальнейшего исследования Петром и его коллегами программного кода ДЭГ.

— Петр, после публикации твоей статьи на «Хабре» и твоего интервью в «Новой газете» ты продолжил исследовать, как устроено электронное голосование, и обнаружил кое-что интересное, о чем ты не рассказывал в предыдущем интервью. Что тебе удалось найти?

— За то время, которое прошло с публикации моей статьи на «Хабре» и интервью «Новой», я и еще много других людей в чате программистов, которые занимаются анализом дистанционного голосования, изучали исходный код. Нам стали понятны ответы на те вопросы, которых не было три недели назад: как у нас устроена система подсчета голосов, как у нас устроено переголосование и как можно технически подделать выборы в этой системе, если захотеть.

— Мы так и не сумели добиться от Артема Костырко внятного ответа на вопрос о том, что же такое собой представляет тайный «второй блокчейн», в который записывается переголосование. Можешь ли ты описать, что тебе удалось понять про этот второй блокчейн?

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

— Называющееся group_id?

— Да, в базу пишется group_id, то есть идентификатор группы (бюллетеней от одного избирателя), и пишется реальное время голосования — когда сервер приема бюллетеней получил голос. И пишется вот тот самый хэш голоса, который можно было сохранить, если вы пользовались инструкцией от «Голоса»* (власти назвали организацию иноагентом). То есть у нас хэш голосования, реальное время голоса и ID группы бюллетеней от этого избирателя. После голосования все бюллетени из одной и той же группы группируются, и из них выбирается последний по времени голос.

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

— Да. Хэш позволяет нам для данного голоса в публичном блокчейне сказать по данным закрытого блокчейна, когда реально был получен этот голос и к какой группе переголосований он относится. То есть позволяет связать эти два блокчейна друг с другом.

— Записывается ли в публичный блокчейн идентификатор group_id, то есть идентификатор, общий для всех голосований данного избирателя?

— Нет, не записывается.

— А записывается ли в публичный блокчейн что-то, что позволяло бы нам выяснить, что этот избиратель легитимен?

— Нет, не записывается.

— Хорошо. Итак, у нас есть второй блокчейн, в который записан group_id, подвергшийся шифрованию, так?

— Да. group_id в открытом виде во время голосования не пишется в закрытый блокчейн, к этому group_id добавляется случайное число и добавляется еще текущее время. Помимо того времени, которое дополнительно пишется в открытом виде. И вот эта тройка чисел шифруется, чтобы для одного и того же избирателя этот зашифрованный текст был бы всякий раз разный. Это делается, чтобы невозможно было понять до завершения голосования, какие голоса принадлежат одному и тому же человеку. Шифруют group_id неким отдельным ключом — не тем, который делится на 7 частей.

— Кому принадлежит ключ зашифрования и расшифрования для group_ id?

— Он просто, по всей видимости, на сервере ДЭГ записан в каком-то текстовом файле. Непонятно, кто на него может смотреть.

— А сервер у кого стоит?

— У нас голосование проводит ДИТ Москвы, поэтому, видимо, в ДИТе.

— Еще раз давай проговорим: и ключ зашифрования group_id, и ключ расшифрования group_id хранится у одного и того же юридического лица, правильно?

— Все так, да. Используется один и тот же ключ, который находится в руках одной и той же организации — ДИТ Москвы.

— Ты сказал, что в публичный блокчейн не пишется никаких параметров, которые позволяли бы определить, что этот голос принадлежит какому-то конкретному избирателю и что этот избиратель легитимен, верно?

— Да, все так.

— Как в таком случае программное обеспечение, которое осуществляет запись в публичный блокчейн, проверяет, что пришедшие из внешнего мира сообщения принадлежат не какому-то прохиндею, который нас хочет обмануть, а легитимному избирателю?

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

— То есть снаружи мы в нее не можем забраться, потому что у нас стоит фильтр в виде Mos.ru. Но предположим, что теперь я злоумышленник, который атакует эту систему изнутри, который имитирует приход на нее сообщения как бы от внешнего пользователя. Что мне нужно для взлома? Кто мне позволит или не позволит записать это сообщение в блокчейн и по какому принципу позволит или не позволит?

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

— Давай еще раз подчеркнем для наших читателей, что этот ключ не тождественен тому ключу, с помощью которого расшифровываются голоса, который делится на семь частей, верно?

— Да, верно.

— И что это не ключ для шифрования group_id?

— Верно.

— Итак, это уже третий ключ, о котором мы говорим. И когда тебе это удалось выяснить?

— Я прочитал исходный код, это, в принципе, было понятно уже сразу. Но захотелось провести эксперимент. Я взял тот исходный код, который был опубликован ДИТ Москвы, и запустил вот этот публичный блокчейн, к которому мы имели доступ и могли смотреть на observer.mos.ru. Я осуществил следующую схему: создал какое-то тестовое голосование, которое мне хотелось провести, создал голосование с двумя кандидатами, Васей и Петей. Дальше я зарегистрировал на это голосование ноль избирателей, выдал ноль бюллетеней и после этого 100 бюллетеней вкинул в урну. Еще раз подчеркну, у нас ноль избирателей зарегистрировано, ноль бюллетеней выдано, но в урне оказалось 100 голосов. И самое удивительное: поскольку эта система не может учитывать переголосования, в ней есть такая дырка, которая позволяет подвести произвольный результат, и через эту дырку я подвожу результат: я вбросил 100 голосов за Васю, а результат подвел — 146 миллионов голосов за Петю и ноль голосов за Васю, за которого я вбрасывал.

— Поясни, в чем заключается дырка? Тем, что ты вбросил бюллетени, ты уже поиздевался над этим блокчейном. Ты мог бы дальше просто, как честный вор, просуммировать то, что ты вбросил. Но ты где-то проковырял еще дыру, в чем эта дыра заключалась?

— Заключается эта дыра в том, что вот те 100 голосов с точки зрения публичного блокчейна — это все настоящие голоса, но с точки зрения того, как в целом система устроена, какие-то из этих 100 голосов могут быть настоящие, какие-то из этих 100 голосов могут быть переголосованными. Поэтому проделана в этом публичном блокчейне еще одна отдельная дырка, которую предполагалось использовать следующим образом: у нас голоса вот эти публичного блокчейна сопоставляются с теми группами избирателей, которые записаны в закрытом, секретном блокчейне, и закрытый блокчейн подводит итоги, и дальше эти итоги копируют из закрытого блокчейна в публичный. А то слишком палевно, видимо, будет, если у нас голосование закончится, а итогов там не будет.

— Подожди, у тебя в закрытом блокчейне все равно не было 146 миллионов голосов, ведь так?

— Да, да.

— То есть правильно ли я понимаю, что после того, как закрытый блокчейн что-то посчитал, имеется стадия, которая осуществляется ручками и живым человеком?

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

— Ручками внес те данные, которые как бы были получены из второго, закрытого блокчейна.

— Да.

— И при этом ты всем сказал, что поскольку отношения у нас джентльменские, то мы тебе должны верить, правильно?

— Примерно так, да.

Читайте также

Соль процентов

Экспертам не дали секретный ключ, который позволил бы разобраться с «переголосованиями». Публикуем полный текст отчета Технической группы, особые мнения и комментарии

— И больше никакого способа проверить, что в закрытом блокчейне, у нас нет, так?

— 146 миллионов голосов было бы совсем палевно, да. А как проверить? У нас джентльменские отношения. Закрытый блокчейн посчитал.

— И этот опыт ты когда провел, сравнительно недавно, да?

— Да, я это сделал сравнительно недавно.

— Ты не думаешь вообще, что руководитель Общественного штаба по наблюдению за выборами Алексей Венедиктов тебе должен премию вручить за обнаружение дыры?

— Я в этой системе предполагаю, что я имею абсолютный к ней доступ. Я организатор выборов в этой системе. Но если я внешний человек, если пользоваться логикой Венедиктова, логикой ДИТ Москвы, то извне взломать ничего не получится. Если ты инсайдер, если у тебя есть доступ…

— На обычных выборах я ни разу не сталкивался с тем, чтобы вброс организовывал бы кто-то помимо избиркома. Потому что если вброс осуществит аутсайдер, то он только все напортит. Ему необходимо содействие избирательной комиссии, чтобы подделать списки избирателей.

Так вот, правильно ли я в итоге понимаю, что для того, чтобы осуществить вброс и/или прямое поправление результатов ручками (а этом два разных способа), тебе всего лишь навсего нужно иметь доступ к ключу организаторов голосования?

— Да. И доступ к ноде блокчейна прямой, чтобы напрямую, в обход всех серверов туда писать.

— Объясни, почему при таком вбросе не возникнет проблемы с Госуслугами? Правильно ли я понимаю, что если злоумышленник таким образом будет вносить записи, он вносит запись, в которой нет никаких сведений об избирателе, правильно?

— Да. Это сделано, чтобы защитить как бы тайну голосования.

— Поэтому публичный блокчейн не может отследить, что запись появилась не с моs.ru, а с потолка?

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

— А скажи, пожалуйста, в хэш транзакции голосования (который, напомним, объединяет и публичный, и непубличный блокчейн), в него никак не пишется никаким скрытым образом voter_id, sudir_id, group_id?

— Нет, ничего не пишется.

— А откуда хэш берется, поясни его происхождение?

— Хэш формируется из содержимого голоса непосредственно. И еще там есть подпись избирателя, но подпись здесь такая немного странная — она прямо вот на каждом новом бюллетене генерируется новая, как если бы вы каждый раз подписывали все время по-разному.

— Правильно ли я понимаю, что нет никакой возможности по подписи избирателя проверить его легитимность?

— В публичном блокчейне — нет.

— Этот хэш каким-то образом инкорпорирует в себя хэши предыдущих транзакций или он строго связан только с этой транзакцией?

— Строго связан только с этой транзакцией, строго только с этим голосом.

— Правильно ли я понимаю, что собственно блокчейновость имеет место, но обепечивается не хэшем голосования, а другими хэшами?

То есть эта база «прошита» дополнительными хэшами насквозь, как книга избирателей прошита ниткой. Благодаря чему из блокчейна невозможно ничего «выкинуть». Но это — другие хэши, не тот хэш, который связывает публичный и непубличный блокчейн.

— Да. Но в принципе ничего необычного в этом нет.

— Итак, мы поняли, что, имея ключ организатора голосования, можно вписать в публичный блокчейн любую информацию о том, что было проголосовано за кого-то, при этом не указывать, кем проголосовано. И тем самым мы обходим необходимость логиниться на Госуслугах, создавать фейковых избирателей, отслеживать, кто из избирателей не голосует, или же взламывать их аккаунты, верно?

— Да. И если приводить бумажный аналог, это можно сравнить с тем, как если бы я пришел на избирательный участок, предварительно напечатав бюллетени на принтере, и вбросил бы эту самостоятельно напечатанную пачку.

— То есть бюллетени, которые не учтены ни при получении их избиркомом, ни при выдаче избирателям, — просто мы из типографии принесли.

— Да.

— В таком случае мы в обычной жизни попали бы на то, что у нас не сошлось бы количество бюллетеней в урне и количество голосующих избирателей: было бы в урне больше бюллетеней, чем избирателей по книгам, и тогда бы урну пришлось аннулировать.

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

— Во-первых, я хочу сказать, что, по публичным данным, именно это и произошло, на что движение «Голос» обратило внимание сразу же после подведения итогов.

Если мы зайдем прямо сейчас на observer.mos.ru, то увидим, что голосов в системе больше, чем выдано бюллетеней.

Так что у нас действительно так и произошло. Объяснение здесь, откуда тут взялись бюллетени, лежит в алгоритме переголосований, потому что какие-то из этих бюллетеней были первые, какие-то вторые, какие-то третьи, какие-то последние. И нам нужно выкинуть все эти бюллетени, которые были переголосованы потом каким-то другим бюллетенем. В моей предлагаемой схеме взлома мы накидали туда голосов, но потом, для того чтобы подвести «хорошие» итоги, нам нужно какие-то выкинуть. Для этого есть удобный механизм в виде второго блокчейна, который позволяет нам сказать, что вброшенные бюллетени относятся к некоему идентификатору группы бюллетеней.

— Напомню нашим читателям, что идентификатор группы бюллетеней одинаковый у всех бюллетеней, которые поступили от данного избирателя.

— Да. И мы можем по ходу голосования писать в публичный блокчейн голоса, которые за провластного кандидата, а в закрытый блокчейн ничего не писать до поры до времени. Когда у нас закончилось голосование, опубликован ключ расшифрования, мы расшифровываем все голоса и смотрим, какие из них за оппозицию. Про вброшенные голоса мы говорим: вот у нас есть голоса, мы запомнили их хэши. После голосования мы внесем эти хэши и в тайный блокчейн. При этом запись мы сделаем в виде химеры: мы возьмем хэш вброшенного бюллетеня (за власть) и group_id бюллетеня за оппозицию. Таким образом, мы «перетрем» ненастоящим бюллетенем, который мы кинули в систему, настоящий бюллетень реального избирателя.

Фото: URA.RU / TASS

Фото: URA.RU / TASS

— Давай еще раз проговорим: когда мы вбрасывали, мы вбрасывали их как бы безымянными. Поэтому не записывали их во второй блокчейн.

Как же нам удалось их потом вставить вот в этот второй блокчейн? Мы же много раз повторили, что блокчейн устроен как прошитый и запечатанный документ, из которого нельзя выдрать или вставить лист, потому что каждая следующая транзакция несет в себе следы предыдущей. Почему же с потайным блокчейном это не работает?

— Во-первых, из-за того, что этот блокчейн сам по себе приватный, никто его никогда не видел. Если мы его перепишем заново с нуля, все равно никто ничего не заметит, вы не видели, как он составлялся, поэтому вы не увидите, что мы его подменили.

— В отличие от публичного блокчейна, который хотя бы раз в полчаса выгружался на observer.

— Да. Во-вторых, можно, например, выключить подсчет голосов между тем моментом, когда мы опубликовали ключи и все (или часть) расшифровали, и моментом, когда надо подводить итоги. Если у нас, например, все не расшифровалось, но ключ уже опубликован, мы останавливаем подсчет голосов, останавливаем все эти блокчейны, расшифровываем голоса и смотрим, какие нам из них не нравятся, и дописываем их в этот закрытый блокчейн так, чтобы выкинуть те голоса, которые нас не устраивают.

— Итак, повторим, как мы сделали химеру: этруски взяли голову змеи и приделали к хвосту льва, а мы взяли хэши от голосований, которые были за оппозицию, нашли в закрытом блокчейне, какой group_id соответствует данному хэшу, потом взяли этот group_id, вставили в новую строчку закрытого блокчейна и хэш заменили на хэш от вброшенного голоса от ноунейма, правильно?

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

— И дальше мы можем честно посчитать приватный блокчейн, после того как мы в него вкинули голоса, и приватный блокчейн нам скажет, что люди голосовали за оппозицию, а меньше чем через день раздумали, верно?

— Да. Такая вот система получилась дырявая.

— Какие еще интересные дырки или странности в этой системе удалось тебе увидеть?

— Мы нашли, что помимо блокчейна-1 (публичного) и блокчейна-2 (тайного) в коде системы ДЭГ предусмотрено ведение некоей «обычной» базы данных. Когда сообщение с голосом приходит в систему, помимо того, чтобы записать его в блокчейн-1 и (часть данных) в блокчейн-2, он еще попадает в отдельную таблицу под названием p_ballot — считай, это просто табличка в Excel. Зачем это делается, из исходного кода абсолютно непонятно. Туда просто пишутся данные, никак не используются.

— Это была странность номер один?

— А теперь странность номер два. Тот сервис, который шифрует идентификаторы группы избирателей, чтобы они зашифрованными попали в блокчейн-2, имеет внутри себя логи. Логи — это специальный текстовый файл, который создается по ходу работы программы, для того чтобы программист понимал, что с ней происходит, что она делает, или мог понимать, где какие ошибки могут возникнуть. Так вот, в нашем случае в этот текстовый файл пишется содержимое следующего рода. На каждый запрос к этому серверу что-то зашифровать он пишет сначала сообщение: зашифровываю вот такой-то текст. Дальше пишет опять в этот же лог: зашифровал этот текст следующим образом. Таким образом, если у вас неправильно настроено логирование в этом сервисе, у вас все пишется в этот лог, а по умолчанию именно так у меня и произошло, то все шифрование теряет абсолютно смысл, потому что вы читаете этот текстовый файл и видите нешифрованный и шифрованный текст одновременно.

— Напоминает анекдот про Штирлица и парашютные стропы.

— Примерно так, да. Странность номер три заключается в том, что идентификатор группы избирателей создается на основе идентификатора в mos.ru. Таким образом, если у нас есть доступ к сайту mos.ru и доступ к всему списку избирателей, мы можем из содержимого закрытого блокчейна или этой странной «таблицы в экселе» понять, кто как голосовал, каждый из двух миллионов человек.

— Дмитрий Нестеров многократно указывал в своих выступлениях на то, что электронное голосование чисто теоретически не может совместить две вещи: проверку легитимности и анонимность. Либо одно, либо другое. Правильно ли я понимаю, что ровно это мы и видим сейчас с двумя блокчейнами? Публичный блокчейн не может проверить легитимность, но при этом соблюдает вашу анонимность, а приватный блокчейн нарушает вашу анонимность (потому что в создании group_id используется идентификатор избирателя с mos.ru), но при этом как бы знает о том, легитимный вы избиратель или нет.

— Примерно так, да.

— Были ли еще сюрпризы в исходном коде?

— Наконец, мы выяснили, что исходный код, который был опубликован, отличается от того, который реально использовался. Сначала заметили, что отличается та часть кода, которая исполняется в браузере пользователя. Дальше мы обнаружили, что в опубликованном коде переголосовывать можно только в текущие сутки, а не через 24 часа после первого голоса (как было на реальных выборах). Я не думаю, что исходный код очень сильно изменялся, но все равно некрасиво со стороны ДИТ.

— И наконец, возвращаясь к тому ключу, с помощью которого в блокчейн можно писать все что угодно, в обход mos.ru или Госуслуг. Есть ли у тебя понимание, исходя из анализа исходного кода и твоих экспериментов, в каких формах мог существовать этот ключ?

— Я напомню, что сейчас мы говорим о ключе подписи организаторов выборов. Это электронно-цифровая подпись, которой организатор выборов подписывает свои сообщения, что это именно он создает голосование, именно он регистрирует избирателей, именно он выдает бюллетень, именно он подводит итоги. Когда я понял значимость этого ключа, у меня сразу возник вопрос: а кто имел доступ к этому ключу и как он формировался?

И для меня было большим расстройством увидеть, что в тестовом голосовании по вопросу общественного транспорта, по вопросу прививок, обязательной вакцинации от коронавируса использовался тот же самый ключ, который использовался на выборах в Госдуму. Это значит, что между 30 июля и 17–19 сентября непонятно какие люди, непонятно на каком основании могли смотреть на этот ключ, могли не смотреть на этот ключ, и это очень расстраивает — то, что этот ключ не пересоздавался для каждого голосования заново, меня очень пугает. Это навевает мысли о плохих практиках программирования в ДИТ Москвы. Во-первых, когда секретные ключи просто переиспользуются, это уже нарушение правил информационной безопасности. Во-вторых, я опасаюсь, что этот ключ мог быть просто записан в текстовый файл, и к нему непонятно какие программисты могли иметь доступ, в непонятно какое время (у нас удалены были конфигурационные файлы из исходного кода, и я не могу это подтвердить или опровергнуть).

Благодарность Петра Жижина за помощь в анализе исходного кода: чату программистов, которые анализировали ДЭГ, и, в частности, Борису Тавадову, и программисту под псевдонимом SinX.

shareprint
Добавьте в Конструктор подписки, приготовленные Редакцией, или свои любимые источники: сайты, телеграм- и youtube-каналы. Залогиньтесь, чтобы не терять свои подписки на разных устройствах
arrow