Бесит, сука

Вот посмотрел я на то, из чего состоит rawStoreBallotTx, передающийся на сервер и сохраняющийся в блокчейн при электронном голосовании в Москве. Разбираться с обширным кодом клиента Exonum на Javascript я не стал, а просто глянул на сами данные.

58 1c c8 b3 ed 95 97 f3 f4 ab c7 76 1d 17 c3 02
a3 d5 3e 59 c8 c7 6c 55 04 4b f0 d3 91 e7 fc c1
00 00 e9 03 06 00 0a 40 64 61 38 35 66 61 66 38
34 66 61 65 62 30 65 39 30 65 66 33 31 61 32 65
37 32 35 32 34 31 35 31 66 62 61 63 30 65 32 66
36 65 36 63 34 39 38 33 36 30 31 33 64 36 34 35
32 39 34 36 39 38 38 64 10 4d 1a 56 0a 14 bc ef
fd 5e 5e a6 8a 6b b2 d9 04 31 dd 51 2e 11 5e 4c
44 e3 12 1a 0a 18 9b 37 cb a5 f0 26 25 ca 74 45
00 21 88 e1 59 ca 56 ee d5 24 dd 25 d3 26 1a 22
0a 20 57 1d b7 6c 24 f5 85 22 7e 75 4c ca e6 97
99 8f f6 22 6b 24 58 94 b9 0d 57 b0 f3 d5 9a 0c
16 01 9d c1 e2 eb 1d 8d 8a cf 12 60 27 3f f0 2e
cc ba 5d 9f 61 e0 ba 51 a7 d2 26 78 82 d1 05 6d
a5 92 93 75 7d 5a c5 23 e0 45 f7 f9 8d 6b ed 4f
33 fe d7 c2 e2 48 77 c6 61 9b 10 86 b3 c7 a7 73
3f 0d

Первые 32 байта – это некий идентификатор под названием accountAddressBlock; дальше начинается что-то непонятное – но резко выделяется довольно длинная последовательность из байт в диапазоне 30-39 и 60-66 – это цифры и буквы от a до f. Выпишем ее отдельно (справа – символы, которым эти байты соответствуют):

                        64 61 38 35 66 61 66 38            da85faf8
34 66 61 65 62 30 65 39 30 65 66 33 31 61 32 65    4faeb0e90ef31a2e
37 32 35 32 34 31 35 31 66 62 61 63 30 65 32 66    72524151fbac0e2f
36 65 36 63 34 39 38 33 36 30 31 33 64 36 34 35    6e6c49836013d645
32 39 34 36 39 38 38 64                            2946988d

Что это? А это как раз voting id, идентификатор голосования – только зачем-то переданный в виде текста. Сразу же хочется обратить внимание на байт 40, стоящий непосредственно перед voting id – да и вообще на всю последовательность 00 00 e9 03 06 00 0a 40 – хочется считать ее неким заголовком, а 40 – длиной поля. По аналогии находим еще три подобных последовательности – “заголовок” 10 4d 1a 56 0a 14 (в кавычках, и я сейчас объясню, почему) предшествует 20 байтам

                                          bc ef
fd 5e 5e a6 8a 6b b2 d9 04 31 dd 51 2e 11 5e 4c
44 e3

Заголовок 12 1a 0a 18 – последовательности из 24 байт:

                  9b 37 cb a5 f0 26 25 ca 74 45
00 21 88 e1 59 ca 56 ee d5 24 dd 25 d3 26

А заголовок 1a 22 0a 20 – 32 байтам:

      57 1d b7 6c 24 f5 85 22 7e 75 4c ca e6 97
99 8f f6 22 6b 24 58 94 b9 0d 57 b0 f3 d5 9a 0c
16 01

Казалось бы, это вся информация для записи в блокчейн? Но нет – не хватает district id, номера “участка” (здесь он принимал логичные значения 77 и 52 для Москвы и Нижнего Новгорода соответственно). Где он мог потеряться? Оказывается, первый “заголовок” из 6 байт – это на самом деле еще и поле с номером участка, 10 4d (4d в шестнадцатиричной системе – это как раз 77).

В оставшихся 64 байтах я какой-то структуры уже не увидел, но в целом для расшифровки голоса они нам уже не нужны. Вспоминаем структуру данных для передачи – это должны быть зашифрованный голос, nonce и публичный ключ пользователя; в используемой для голосования библиотеке NaCl длина nonce и публичного ключа как раз составляет 24 и 32 байта соответственно, 20 байт – это длина сообщения. Кажется, мы нашли все необходимое? Проверяем несложной программой:

unsigned char message[] = {
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0xbc, 0xef, 0xfd, 0x5e, 0x5e, 0xa6, 0x8a, 0x6b,
    0xb2, 0xd9, 0x04, 0x31, 0xdd, 0x51, 0x2e, 0x11,
    0x5e, 0x4c, 0x44, 0xe3
}; // first 16 bytes must be 0

unsigned char nonce[] = {
    0x9b, 0x37, 0xcb, 0xa5, 0xf0, 0x26, 0x25, 0xca,
    0x74, 0x45, 0x00, 0x21, 0x88, 0xe1, 0x59, 0xca,
    0x56, 0xee, 0xd5, 0x24, 0xdd, 0x25, 0xd3, 0x26
};

unsigned char public_key[] = {
    0x57, 0x1d, 0xb7, 0x6c, 0x24, 0xf5, 0x85, 0x22,
    0x7e, 0x75, 0x4c, 0xca, 0xe6, 0x97, 0x99, 0x8f,
    0xf6, 0x22, 0x6b, 0x24, 0x58, 0x94, 0xb9, 0x0d,
    0x57, 0xb0, 0xf3, 0xd5, 0x9a, 0x0c, 0x16, 0x01
};

unsigned char dit_private_key[] = {
    0xdb, 0x77, 0xd6, 0x2f, 0xc8, 0x87, 0x26, 0xf1,
    0xc5, 0xa6, 0xb7, 0x9b, 0x00, 0x3b, 0x3b, 0xca,
    0x83, 0x34, 0x9e, 0x33, 0x43, 0x37, 0xde, 0x84,
    0xbd, 0x17, 0x34, 0x4a, 0xb6, 0x01, 0xdb, 0x74
};

unsigned char buf[sizeof(message)] = { 0 };

int res, vote;

res = crypto_box_open(buf, message, sizeof(message), nonce, public_key, dit_private_key);
vote = *((int*)(buf + crypto_secretbox_ZEROBYTES));
printf("%x\r\n", vote);

И действительно – расшифрованное сообщение содержит 4 байта 1a d5 be 0d – голос “против” на этом голосовании.

Так вот, возвращаясь к тому, что бесит. Во-первых, бесят просранные талантливыми фронтендерами 32 байта – зачем засовывать voting id в виде строки? Во-вторых – бесит полное отсутствие описание формата передаваемых данных – вот я его разобрал довольно элементарным способом, но пока так и не знаю о назначении еще 64 байт в конце – похоже, что это как-то связано с блокчейном и криптографической подписью для него, но я не уверен.

Боженька, побей их всех по голове ЕСПД.

4 комментария

  1. squeezedorange пишет:

    На моей памяти только одно частное предприятие готовило документацию в соответствие с ЕСКД и ЕСПД. И то, потому что на испытаниях ее реально могли взять и проверить левые люди. При этом содержание могло быть весьма далёким от реальности. А уж после пусконаладки у заказчика так и вообще. Так что чего ты хочешь – тут вообще даже никакого вреда не предвидится. Кроме твоих моральных травм )
    Ну ок, выборка не прям великая – контор 20, из них на госах около половины.

    • Я был очевидцем “жесткой” приемки системы, где буквально каждый пункт “Программы и методики испытаний” читался под микроскопом. Все операции выполняли люди от заказчика, пользуясь имеющимися “Руководствами оператора/системного программиста/etc”, любая неясность в документах или подсказки от разработчиков записывались. На проверке требований к восстановлению после сбоев их главный тупо выдернул из сервера жесткий диск и включил секундомер :) Но там была явная установка “завалить”, конечно.