Перейти к основному содержимому

Генерация случайных чисел

warning

Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.

Генерация случайных чисел - распространенная задача, которая может понадобиться Вам в самых разных проектах. Возможно, Вы уже встречали функцию random() в документации FunC, но обратите внимание на то, что ее результат можно легко предсказать, если не использовать некоторые дополнительные приемы.

Как кто-то может предсказать случайное число?

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

Эти алгоритмы обычно требуют от Вас указать значение seed'a, которое будет использовано для генерации последовательности псевдослучайных чисел. Таким образом, если Вы запустите одну и ту же программу с одним и тем же seed несколько раз, Вы неизменно получите один и тот же результат. В TON seed для каждого блока разное.

Поэтому, чтобы предсказать результат работы функции random() в смарт-контракте, Вам просто нужно знать текущий seed блока, что невозможно, если Вы не являетесь валидатором.

Просто используйте randomize_lt().

Чтобы сделать генерацию случайных чисел непредсказуемой, Вы можете добавить текущее Логическое время к seed'y, чтобы разные транзакции имели разные seed'ы и результаты.

Просто добавьте вызов randomize_lt() перед генерацией случайных чисел, и Ваши случайные числа станут непредсказуемыми:

randomize_lt();
int x = random(); ;; users can't predict this number

Однако следует учитывать, что валидаторы или коллаторы все равно могут повлиять на результат случайного числа, поскольку они определяют seed текущего блока.

Существует ли способ защиты от манипуляций со стороны валидаторов?

Чтобы предотвратить (или хотя бы усложнить) подмену seed'a валидаторами, Вы можете использовать более сложные подходы. Например, Вы можете пропустить один блок перед генерацией случайного числа. Если пропустить один блок, seed будет меняться менее предсказуемым образом.

Пропуск блоков это не сложно. Вы можете сделать это, просто отправив сообщение в Мастерчейн и обратно в воркчейн Вашего контракта. Давайте рассмотрим простой пример!

предупреждение

Не используйте этот пример контракта в реальных проектах, вместо этого напишите свой собственный.

Основной контракт в любом воркчейне

Давайте в качестве примера напишем простой лотерейный контракт. Пользователь отправит на него 1 TON и с вероятностью 50% получит 2 TON обратно.

;; set the echo-contract address
const echo_address = "Ef8Nb7157K5bVxNKAvIWreRcF0RcUlzcCA7lwmewWVNtqM3s"a;

() recv_internal (int msg_value, cell in_msg_full, slice in_msg_body) impure {
var cs = in_msg_full.begin_parse();
var flags = cs~load_uint(4);
if (flags & 1) { ;; ignore bounced messages
return ();
}
slice sender = cs~load_msg_addr();

int op = in_msg_body~load_uint(32);
if ((op == 0) & equal_slice_bits(in_msg_body, "bet")) { ;; bet from user
throw_unless(501, msg_value == 1000000000); ;; 1 TON

send_raw_message(
begin_cell()
.store_uint(0x18, 6)
.store_slice(echo_address)
.store_coins(0)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page)
.store_uint(1, 32) ;; let 1 be echo opcode in our contract
.store_slice(sender) ;; forward user address
.end_cell(),
64 ;; send the remaining value of an incoming msg
);
}
elseif (op == 1) { ;; echo
throw_unless(502, equal_slice_bits(sender, echo_address)); ;; only accept echoes from our echo-contract

slice user = in_msg_body~load_msg_addr();

{-
at this point we have skipped 1+ blocks
so let's just generate the random number
-}
randomize_lt();
int x = rand(2); ;; generate a random number (either 0 or 1)
if (x == 1) { ;; user won
send_raw_message(
begin_cell()
.store_uint(0x18, 6)
.store_slice(user)
.store_coins(2000000000) ;; 2 TON
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page)
.end_cell(),
3 ;; ignore errors & pay fees separately
);
}
}
}

Разверните этот контракт в любой нужный Вам воркчейн (возможно, Basechain), и готово!

Является ли этот метод на 100% безопасным?

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

В последнем обновлении TVM введение новых значений в регистр c7 может еще больше повысить безопасность генерации случайных чисел. В частности, обновление добавляет в регистр c7 информацию о последних 16 блоках мастерчейна.

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

Более подробную информацию об этом обновлении TVM смотрите в разделе Обновление TVM.