Мало кто нынче не слышал о криптовалютах и, в частности, Bitcoin. В 2014-м году, на волне интереса к биткоину, появилась новая криптовалюта — Ethereum. Сегодня, в 2017-м, она является второй по капитализации после биткоина. Одним из важнейших её отличий от биткоина является использование тьюринг-полной виртуальной машины — EVM. Подробнее про эфир можно прочитать в его Yellow Paper.

Смарт-контракты Ethereum обычно пишут на языке Solidity. На Хабре уже были статьи про написание и тестирование смарт-контрактов, например 1, 2, 3. А про связь смарт-контракта с сайтом можно почитать, например, статью о создании простейшей голосовалки на смарт-контракте. В этой статье используется встроенный в кошелёк Mist броузер, но то же самое можно делать используя плагин к Chrome, например MetaMask. Именно так, через MetaMask, и работает игра, которую мы будем исследовать.

Игра является реализацией реализацией европейской рулетки: на поле 37 клеток, пронумерованных от 0 до 36. Можно делать ставку на конкретный номер либо на набор номеров: чётные/нечётные, красное/черное, 1-12, 1-18 и т.д. В каждом раунде можно сделать несколько ставок путём добавления жетона (стоимостью 0.01 ETH ? $0.5) на соответствующее поле игрового стола. Каждому полю соответствует коэффициент выигрыша. Например, ставке «на красное» соответствует коэффициент 2 — то есть заплатив 0.01 ETH вы, в случае выигрыша, получите 0.02 ETH. А если поставите на зеро, то коэффициент будет 36: заплатив тот же 0.01 ETH за ставку вы получите 0.36 в случае выигрыша.

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

Когда все ставки сделаны, игрок нажимает кнопку «Играть» и, через MetaMask, отправляет пари* в Ethereum-блокчейн, на адрес смарт-контракта игры. Контракт определяет выпавшее число, рассчитывает результаты ставок и, в случае необходимости, отсылает выигрыш игроку.
* — Я буду использовать термин пари для обозначения набора ставок (т.е. пар тип ставки — количество жетонов на ставку), которые игрок делает в рамках одного раунда. Если вы знаете более корректный термин, напишите мне пожалуйста, я исправлю.

Для того, чтобы понять честно ли работает игра (то есть, не манипулирует ли казино определением выпавшего числа в свою пользу) проанализируем работу смарт-контракта.

Его адрес указан на сайте игры. Кроме того, перед подтверждением оплаты можно проверить на какой адрес будет отправлено пари. Я проанализирую контракт по адресу 0xDfC328c19C8De45ac0117f836646378c10e0CdA3. Etherscan показывает его код, а для удобного просмотра можно использовать Solidity Browser.

Работа контракта начинается с вызова функции placeBet():

Простыня кода
function placeBet(uint256 bets, bytes32 values1,bytes32 values2) public payable
{
   if (ContractState == false)
   {
     ErrorLog(msg.sender, "ContractDisabled");
     if (msg.sender.send(msg.value) == false) throw;
     return;
   }

   var gamblesLength = gambles.length;

   if (gamblesLength > 0)
   {
      uint8 gamblesCountInCurrentBlock = 0;
      for(var i = gamblesLength - 1;i > 0; i--)
      {
        if (gambles[i].blockNumber == block.number) 
        {
           if (gambles[i].player == msg.sender)
           {
               ErrorLog(msg.sender, "Play twice the same block");
               if (msg.sender.send(msg.value) == false) throw;
               return;
           }

           gamblesCountInCurrentBlock++;
           if (gamblesCountInCurrentBlock >= maxGamblesPerBlock)
           {
              ErrorLog(msg.sender, "maxGamblesPerBlock");
              if (msg.sender.send(msg.value) == false) throw;
              return;
           }
        }
        else
        {
           break;
        }
      }
   }
   
   var _currentMaxBet = currentMaxBet;

   if (msg.value < _currentMaxBet/256 || bets == 0)
   {
      ErrorLog(msg.sender, "Wrong bet value");
      if (msg.sender.send(msg.value) == false) throw;
      return;
   }

   if (msg.value > _currentMaxBet)
   {
      ErrorLog(msg.sender, "Limit for table");
      if (msg.sender.send(msg.value) == false) throw;
      return;
   }

   GameInfo memory g = GameInfo(msg.sender, block.number, 37, bets, values1,values2);

   if (totalBetValue(g) != msg.value)
   {
      ErrorLog(msg.sender, "Wrong bet value");
      if (msg.sender.send(msg.value) == false) throw;
      return;
   }       

   address affiliate = 0;
   uint16 coef_affiliate = 0;
   uint16 coef_player;
   if (address(smartAffiliateContract) > 0)
   {        
     (affiliate, coef_affiliate, coef_player) = smartAffiliateContract.getAffiliateInfo(msg.sender);   
   }
   else
   {
     coef_player = CoefPlayerEmission;
   }

   uint256 playerTokens;
   uint8 errorCodeEmission;
   
   (playerTokens, errorCodeEmission) = smartToken.emission(msg.sender, affiliate, msg.value, coef_player, coef_affiliate);
   if (errorCodeEmission != 0)
   {
      if (errorCodeEmission == 1) 
        ErrorLog(msg.sender, "token operations stopped");
      else if (errorCodeEmission == 2) 
        ErrorLog(msg.sender, "contract is not in a games list");
      else if (errorCodeEmission == 3) 
        ErrorLog(msg.sender, "incorect player address");
      else if (errorCodeEmission == 4) 
        ErrorLog(msg.sender, "incorect value bet");
      else if (errorCodeEmission == 5) 
        ErrorLog(msg.sender, "incorect Coefficient emissions");
      
      if (msg.sender.send(msg.value) == false) throw;
      return;
   }

   gambles.push(g);

   PlayerBet(gamblesLength, playerTokens); 
}

Для новичков в Solidity поясню, что модификаторы public и payable означают, что функция является частью API контракта и что при её вызове можно отправить эфир. При этом информация об отправителе и количестве отправленного эфира будет доступна через переменную msg.

Параметрами вызова является битовая маска типов ставок и два 32-байтных массива с количеством жетонов на каждый из типов. Догадаться об этом можно посмотрев на определение типа GameInfo и функций getBetValueByGamble(), getBetValue().

Ещё одна простыня кода, поменьше
struct GameInfo
{
    address player;
    uint256 blockNumber;
    uint8 wheelResult;
    uint256 bets;
    bytes32 values;
    bytes32 values2;
}

// n - number player bet
// nBit - betIndex
function getBetValueByGamble(GameInfo memory gamble, uint8 n, uint8 nBit) private constant returns (uint256) 
{
  if (n <= 32) return getBetValue(gamble.values , n, nBit);
  if (n <= 64) return getBetValue(gamble.values2, n - 32, nBit);
  // there are 64 maximum unique bets (positions) in one game
  throw;
}
// n form 1 <= to <= 32
function getBetValue(bytes32 values, uint8 n, uint8 nBit) private constant returns (uint256)
{
    // bet in credits (1..256) 
    uint256 bet = uint256(values[32 - n]) + 1;

    if (bet < uint256(minCreditsOnBet[nBit]+1)) throw;   //default: bet < 0+1
    if (bet > uint256(256-maxCreditsOnBet[nBit])) throw; //default: bet > 256-0      

    return currentMaxBet * bet / 256;        
}

Отмечу, что getBetValue() возвращает сумму ставки уже не в жетонах, а в wei.

Первым делом placeBet() проверяет, что контракт не выключен и затем начинаются проверки пари.
Массив gambles является хранилищем всех сыгранных в данном контракте пари. placeBet() находит все пари в своём блоке и проверяет не присылал ли данный игрок другое пари в этом блоке и не превышено ли разрешенное количество пари на блок. Затем проверяются ограничения на минимальную и максимальную сумму ставки.

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

Далее переданные в функцию параметры сохраняются в структуре GameInfo.Здесь нам важно, что поле wheelResult инициализируется числом 37.

После ещё одной проверки, что сумма ставок совпадает с присланным количеством эфира происходит распределение токенов RLT, обрабатывается реферальная программа, информация о пари сохраняется в gambles и создаётся событие PlayerBet с номером и суммой пари, которое затем видно в веб-части игры.

Про токены
При каждой ставке игроку выдаётся определённое количество RLT, Ethereum-токенов, которыми определяется право владельца токенов на получение дивидендов с прибыли, полученной авторами игры. Подробнее об этом — читайте White Paper.

Дальнейшая жизнь пари начинается с вызова функции ProcessGames(), который, после появления новой ставки, выполняется, в настоящее время, с адреса 0xa92d36dc1ca4f505f1886503a0626c4aa8106497. Такие вызовы хороши видны при просмотре списка транзакций контракта игры: у них Value = 0.
Код ProcessGames
function ProcessGames(uint256[] gameIndexes, bool simulate) 
{
  if (!simulate)
  {
     if (lastBlockGamesProcessed == block.number)  return;
     lastBlockGamesProcessed = block.number;
  }

  uint8 delay = BlockDelay;
  uint256 length = gameIndexes.length;
  bool success = false;
  for(uint256 i = 0;i < length;i++)
  {      
     if (ProcessGame(gameIndexes[i], delay) == GameStatus.Success) success = true;         
  }      
  if (simulate && !success) throw;
}
В параметрах вызова передаётся массив с номерами пари, которые требуют расчёта, и для каждого вызывается ProcessGame.

function ProcessGame(uint256 index, uint256 delay) private returns (GameStatus)
{            
  GameInfo memory g = gambles[index];
  if (block.number - g.blockNumber >= 256) return GameStatus.Stop;

  if (g.wheelResult == 37 && block.number > g.blockNumber + delay)
  {            
     gambles[index].wheelResult = getRandomNumber(g.player, g.blockNumber);
             
     uint256 playerWinnings = getGameResult(gambles[index]);
     if (playerWinnings > 0) 
     {
        if (g.player.send(playerWinnings) == false) throw;
     }

     EndGame(g.player, gambles[index].wheelResult, index);
     return GameStatus.Success;
  }

  return GameStatus.Skipped;
}

Параметрами вызова являются номер ставки и количество блоков которое должно пройти между ставкой и её обработкой. При вызове из ProcessGames() или ProcessGameExt() этот параметр в настоящее время равен 1, это значение можно узнать из результата вызова getSettings().

В случае, если номер блока, в котором происходит обработка, больше чем на 255 блоков отстоит от блока пари, оно не может быть обработано: хэш блока доступен только для последних 256 блоков, а он нужен для определения выпавшего числа.

Далее выполняется проверка не был ли уже рассчитан результат игры (помните, wheelResult инициализровался числом 37, которое выпасть не может?) и прошло ли уже необходимое количество блоков.

Если условия выполнены, производится вызов getRandomNumber() для определения выпавшего числа, вызовом getGameResult() рассчитывается выигрыш. Если он не нулевой, эфир посылается игроку: g.player.send(playerWinnings). Затем создаётся событие EndGame, которое может быть прочитано из веб-части игры.

Посмотрим самое интересное, то как определяется выпавшее число: функцию getRandomNumber().

function getRandomNumber(address player, uint256 playerblock) private returns(uint8 wheelResult)
{
    // block.blockhash - hash of the given block - only works for 256 most recent blocks excluding current
    bytes32 blockHash = block.blockhash(playerblock+BlockDelay); 
    
    if (blockHash==0) 
    {
      ErrorLog(msg.sender, "Cannot generate random number");
      wheelResult = 200;
    }
    else
    {
      bytes32 shaPlayer = sha3(player, blockHash);

      wheelResult = uint8(uint256(shaPlayer)%37);
    }    
}

Её аргументы — адрес игрока и номер блока, в котором была сделана ставка. Первым делом функция получает хэш блока, отстоящего от блока ставки на BlockDelay блоков в будущее.

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

Далее рассчитывается SHA-3 от склейки адреса игрока и полученного хэша блока. Выпавшее число вычисляется путём взятия остатка от деления результата SHA-3 на 37.

С моей точки зрения, алгоритм вполне честный и казино никакого преимущества перед игроком не имеет.
Почему казино приносить владельцам прибыль?
На поле 37 клеток. Предположим, я захочу сделать 100 000 ставок по одному жетону на одно конкретное поле.
Вероятно, примерно 2703 раз я выиграю, а все остальные разы проиграю. При этом за выигрыши от казино я получу 2703*36 = 97 308 жетонов. А 2692 жетона, потраченные мною на ставки, останутся у казино.
Аналогичные расчёты можно провести для всех остальных типов ставок.


Интересно посмотреть, также, как рассчитывается выигрыш. Как мы видели, это делает функция getGameResult().

function getGameResult(GameInfo memory game) private constant returns (uint256 totalWin) 
{
    totalWin = 0;
    uint8 nPlayerBetNo = 0;
    // we sent count bets at last byte 
    uint8 betsCount = uint8(bytes32(game.bets)[0]); 
    for(uint8 i=0; i<maxTypeBets; i++)
    {                      
        if (isBitSet(game.bets, i))
        {              
          var winMul = winMatrix.getCoeff(getIndex(i, game.wheelResult)); // get win coef
          if (winMul > 0) winMul++; // + return player bet
          totalWin += winMul * getBetValueByGamble(game, nPlayerBetNo+1,i);
          nPlayerBetNo++; 

          if (betsCount == 1) break;
          betsCount--;
        }
    }        
}

Параметром сюда передаётся структура GameInfo с данными о рассчитываемом пари. А её поле wheelResult уже заполнено выпавшим числом.

Видим цикл по всем типам ставок, в котором проверяется битовая маска game.bets и если бит проверяемого типа установлен, то запрашивается winMatrix.getCoeff(). winMatrix — это контракт по адресу 0x073D6621E9150bFf9d1D450caAd3c790b6F071F2, загруженный в конструкторе SmartRoulettee().

В качестве параметра этой функции передаётся комбинация типа ставки и выпавшего числа:

// unique combination of bet and wheelResult, used for access to WinMatrix
function getIndex(uint16 bet, uint16 wheelResult) private constant returns (uint16)
{
  return (bet+1)*256 + (wheelResult+1);
}

Разбор кода контракта WinMatrix я оставлю вам в качестве домашнего задания, но ничего неожиданного там нет: генерируется матрица коэфициентов и при вызове getCoeff() возвращается нужный. При желании его легко проверить вызовом этой функции вручную на странице контракта.
Поделиться с друзьями
-->

Комментарии (45)


  1. janatem
    08.04.2017 20:13

    Выпавшее число вычисляется путём взятия остатка от деления результата SHA-3 на 37.

    Это не самый правильный способ. Во-первых, множество возможных значений хэша обычно является степенью двойки, что не может делиться на 37. Так что некоторые остатки будут выпадать чуть-чуть чаще других. Разница совсем ничтожна, но как-то неэстетично. Во-вторых, доказать, что значения данного хэша перемешаны равномерно — очень нетривиальная задача. Хотя обычно по построению хэш-функции «хорошо» перемешивают.


    Предлагаю универсальный способ генерации случайного результата. Пусть каждый заинтересованный участник (в данном случае это игрок и казино) сгенерируют любым способом число от 0 до 36 (рекомендуемый способ — посредством своего автономного ГСЧ, хотя это не обязательно), затем вычислит от него хэш заданной хэш-функцией и вышлет его организатору. Собранные хэши должны быть помещены в блокчейн, чтобы все их видели. Затем каждый участник высылает свое сгенерированное число, что тоже помещается в блокчейн, чтобы все могли проверить, что хеши были получены именно из этих чисел. Результатом объявляется сумма этих чисел по модулю 37. Нужно только немного побороться с теми, кто вышлет хэш, но зажмет свое исходное число, заблокировав тем самым всю процедуру. Но лекарство довольно очевидно.


    1. pash7ka
      08.04.2017 22:12

      Если разница о которой вы говорите — меньше 2.7%, то это не проблема для рассматриваемого казино, а лишь маленький бонус для игрока, который о ней знает. :)

      Если я правильно понял описанный вами алгоритм, на каждое пари придётся совершить две транзакции: одна для хэшей, другая для сгенерированных чисел. Это, во первых, увеличит стоимость ставки (которая = стоимости жетонов + плата за транзакцию). А во вторых, из-за того что время генерации блока в хотя и бывает, в среднем 15 секунд, зачастую доходит полуминуты. А одна из самых частых жалоб на рассмотренную реализацию, как я понимаю — именно низкая скорость. Обычно ставка проходит за 30-60 секунд. Если ждать лишний блок — станет до 90 секунд, это многих раздражает.


      1. janatem
        09.04.2017 00:26

        Разумеется, там и близко нет 2.7%. Например, для 256-битного хэша нетрудно посчитать. Применяя теорему Ферма, имеем: 2^256 = 16 (mod 37). То есть каждый из остатков с 0 по 16 будет попадаться с вероятностью (x+1)/2^256, а каждый из оставшихся остатков — с вероятностью x/2^256, где x = ?2^256/37?. Итоговый дисбаланс (отношение вероятностей) составляет порядка 2^{-251}.


        Да, действительно, нужно ждать две транзакции. Впрочем, вместо блокчейна можно использовать более слабые механизмы для публикации. Но, с другой стороны, в изначальном алгоритме зачем-то нужно ждать BlockDelay блоков, в то время как предлагаемые мной две транзакции вполне могут попасть в соседние блоки, то есть ждать даже меньше.


        1. pash7ka
          09.04.2017 00:44
          +1

          BlockDelay в настоящее время равен 1. Если бы он был равен нулю, то майнер мог бы вместо изменения nonce блока менять свою ставку, и с определённой долей вероятности мог бы не только получить вознаграждение за блок, но и выиграть в рулетку.
          При этом ставка гарантированно будет рассчитана в BlockDelay блоке после её принятия.


          С вашем-же предложением — как сервер, так и игрок могут не присылать свои числа довольно долго.
          Кстати, если игрок не прислал число — ок, его ставка проигрывает. А если число не прислал сервер? А если он его не прислал не потому, что увидел ставку игрока и понял что проиграл, а потому что DDoS?


    1. pash7ka
      08.04.2017 22:21
      +3

      И ещё один важный момент. В рассмотренной реализации у казино нет никакого сервера, с автономным ГСЧ. Всё что доступно смарт-контракту это блокчейн. Сервер, на котором лежит UI игры, никак не участвует в процессе принятия ставок.


    1. pash7ka
      08.04.2017 23:53

      Попытался найти информацию о пригодности SHA-3 в качестве ГСЧ, её на удивление очень мало.
      Из самого полезного: Statistical Analysis of Reduced Round Compression Functions of SHA-3 Second Round Candidates, Why not SHA-3?, Sponge-based pseudo-random number generators.
      Насколько я понял, она считается вполне подходящей, а код типа r = sha3(blockash, nonce) % max — общепринятая практика.


      1. janatem
        09.04.2017 00:44
        +2

        Во-первых, есть общие соображения (по фон Нейману):


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

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


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


        1. pash7ka
          09.04.2017 01:05

          Согласен, анализ таких решений — задача непростая. Кстати, спасибо за пояснение про вероятности веткой выше.


          Про атаку на данный алгоритм вы не совсем правы. Надо сгенерировать BlockDelay + 1 связанных блоков, таких что в первом — выигрышная ставка, которая рассчитывается на основе хэша последнего. При этом изменение ставки требует пересчета всей цепочки.


          Пусть t — среднее время поиска блока в сети.
          Пусть p — вероятность, что наш конкретный майнер найдет блок за время t.
          Если я правильно понимаю, то вероятность того, что майнер за время t успеет найти BlockDelay + 1 взаимосвязанных блоков равняется p ^ (BlockDelay + 1).


          И это ещё если он найдет способ одновременно создавать ставку и blockhash последнего блока… Не представляю как это сделать.


          1. janatem
            09.04.2017 02:19

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


            1. pash7ka
              09.04.2017 03:23

              Ок, давайте считать. По данным Etherscan в день майнеры получают примерно 30000 ETH, которые достаются им за примерно 6000 блоков. То есть награда за блок — примерно 5 ETH.

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

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

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

              Но мне, всё же, кажется это весьма извращённый метод играть в казино…


              1. pash7ka
                09.04.2017 03:31
                +1

                Upd. Ошибся: награду за Uncle получит не тот, кто его нашел, а тот кто включил его в свой. Значит нашему игроку это ничем не поможет: не опубликовать блок = потерять 5 ETH.


              1. janatem
                10.04.2017 12:03

                Из этих выкладок можно сделать оценку снизу на то, какой должна быть ставка в казино, чтобы такая атака была оправданной. Правда, из-за того, что, во-первых, казино берет себе 1/37, и, во-вторых, невелики вероятности нахождения блока и включения его в цепочку, эта стратегия может потребовать очень много ресурсов: огромная ставка плюс огромные мощности.


                1. pash7ka
                  10.04.2017 16:22

                  В нашем случае максимальная ставка ограничена — 256 жетонов на пари. И 5 пари на блок, то есть 12.8 ETH на блок.


  1. Lailore
    08.04.2017 21:36
    -1

    Самый легкий способ доказать честность: перед ставками и игрой отдавать запороленный архив, в котором результат. После ставок отдавать пароль. Почему так не делают?


    1. pash7ka
      08.04.2017 21:59
      +3

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


      1. Lailore
        08.04.2017 22:26
        -1

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


        1. pash7ka
          08.04.2017 22:34
          +1

          Такое очень много где есть. Например в казино Bitsler сервер генерирует случайное число (Seed server) и заранее показывает эго хэш. Так же клиент видит свой Client seed и может его перегенерировать. Плюс используется nonce — порядковый номер ставки игрока.


          И далее, для игры в Multicolor рулетку:


          $seed = $serverSeed.'-'.$clientSeed.'-'.$nonce;
          do {
               $seed = sha1($seed);
               $lucky = hexdec(substr($seed,0,8));
          } while ($lucky > 4294960000);
          
          $luckyNumber = ($lucky % 100000) / 1000;
          
          if ($luckyNumber < 0)
               $luckyNumber = -$luckyNumber;
          
          echo $luckyNumber;


    1. pash7ka
      08.04.2017 22:27
      +1

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


      1. Zolg
        09.04.2017 17:07
        +1

        Случайность (вернее — непредсказуемость) ставок нужна в первую очередь самому казино. Случайные результаты гарантируют (на сколь-либо продолжительном промежутке времени) казино прибыль в 2,7% от сделанных ставок. Для казино при этом рулетка — не азартная игра, а автомат по получению денег. Выигратьывать у казино при этом невозможно.
        Если же казино будет подсовывать неслучайные результаты и делать это честным образом (т.е. 'манипулировать со случайностью' ДО сделанных ставок), то обманет казино в первую очередь само себя, т.к. те, чье поведение подвержено манипуляциям может быть и будут чаще проигрывать, но те, кто способен если не отгадать эти манипуляции, то хотя бы провести статистический анализ эн предыдущих игр, наверняка будут чаще выигрывать.


        1. pash7ka
          09.04.2017 23:42

          Вы абсолютно правы, что для владельцев казино это не азартная игра, а бизнес.
          Вместе с тем, может так случиться что владелец решит увеличить свою прибыль сверх этих 2.7% за счёт своего понимания человеческой психологии. Разумеется, это риск. Но кто сказал, что владелец казино — не азартный человек?
          Допустим, казино формирует серии псевдо-случайных результатов персонально для каждого игрока по 100 штук за раз. Изначально результаты формируются полностью случайно. Однако по мере того, как игрок делает ставки некий чёрный ящик в казино анализирует поведение игрока и, в случае если понимает что игрок следует определённой стратегии, следующую серию результатов может сформировать спецальным образом.

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


          1. Zolg
            10.04.2017 11:20
            +1

            Представьте себе крайний случай — нет никаких алгоритмов и никакой рулетки. Играет два человека:
            Первый загадывает (пишет на бумажке) число от 0 до 36
            Второй делает ставку
            Первый называет загаданное число
            Ставки и выигрыши — по рулеточным правилам.
            При загадывании числа первый может пользоваться чем угодно, от TRNG, до учебника психологии.

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


            1. pash7ka
              10.04.2017 16:37

              Я бы предпочёл сесть за стол с равномерным случайным распределением, т.к. считаю что это минимизирует преимущество первого игрока, сводит его к тем самым 2.7%.

              А на месте первого игрока я бы сначала использовал TRNG и запустил бы нейронную сеть, тренируя её на ставках соперника в зависимости от N предыдущих чисел, чтобы она пыталась их предсказывать. И когда счёл бы, что сеть достаточно натренирована, попробовал бы с её помощью исключать числа которые, предположительно, дадут второму выигрыш.


              1. Zolg
                10.04.2017 17:19
                +2

                Поздравляю, вы выбрали худший для себя вариант.

                Играя вторым номером вы всегда сможете свести преимущество первого игрока к 2,7%. Просто сами используйте TRNG для ставок.
                И можете точно так же, как первый номер анализирует ваши ставки, анализировать его 'результаты'. Если на каком-то этапе анализа захотите — перейдете к активным действиям. При этом у вас есть важное преимущество — размер ставки выбираете вы.


                1. pash7ka
                  10.04.2017 17:54

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

                  Впрочем, лично мне интересно роботов писать… А в случае этой рулетки — оно ещё и разрешено организаторами.


                  1. Zolg
                    10.04.2017 18:13

                    интересно проверить свою интуицию и везение
                    Вы всерьез собрались противопоставлять интуицию и везение TRNG?
                    Интуицию, везение и интеллект имеет смысл противопоставлять другому интеллекту (что есть вариант 2).

                    А в случае этой рулетки — оно ещё и разрешено организаторами.
                    Разговор-то не за эту рулетку, а за то, что самое глупое, что может сделать казино — вносить априорную неслучайность в результаты.


                    1. pash7ka
                      10.04.2017 18:20

                      Нет, я как раз не собираюсь сидеть и играть с TRNG. Но именно так делает значительная часть игроков в рулетку, Dice и прочие игры с автоматами.

                      Разговор начался с вопроса о том, имеет ли смысл владельцу казино заменять TRNG на что-то другое. И моя позиция заключается в том, что если игрок — человек, то такая замена может быть выгодна.


                      1. Zolg
                        10.04.2017 18:21
                        +1

                        если будете открывать казино — дайте знать


                        1. pash7ka
                          10.04.2017 18:22

                          Договорились!


    1. pilyugin
      08.04.2017 22:37

      пароль может содержать зашифрованные результаты, нет?


      1. pash7ka
        08.04.2017 22:38

        Какой пароль вы имеет в виду?


    1. Tantacula
      09.04.2017 01:31

      Казино, даже онлайн, нет смысла обманывать игроков — математическая вероятность и психология игрока на их стороне. Обманите один раз — потеряете игрока, дадите ему честную вероятность выигрыша — он будет постоянно вам все свои деньги носить. Столбики и по 95% выигрыша выдавали, куда выше, чем в рулетке.


      1. pash7ka
        09.04.2017 01:39
        +1

        Согласен, обманывать смысла нету. Однако, не зря говорят: доверяй, но проверяй.
        Лично для меня анализ этого смарт-контракта был ещё и способом разобраться в Solidity.


      1. zxweed
        09.04.2017 09:53

        ага, то-то в России среди владельцев таких столбиков самыми популярными были те, которые вообще никакого выигрыша не выдавали, тупо забирая хозяину все 100% вкинутых денег. Заодно на диспенсере монет экономия…


        1. Tantacula
          10.04.2017 03:55

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


      1. Zolg
        09.04.2017 17:11

        Столбики и по 95% выигрыша выдавали, куда выше, чем в рулетке.
        Незаряженная 'европейская' (с одним zero) рулетка возвращает 36/37, т.е. ~97,3%


        1. Tantacula
          10.04.2017 03:45

          Да, был неправ относительно рулетки.


  1. rPman
    09.04.2017 13:18

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

    p.s. как можно потестировать игру, не загружая блокчейн ethereum (под текущим его ддос это проблематично) или где его можно загрузить в виде архива?


    1. pash7ka
      09.04.2017 14:10

      Протестировать игру можно на её сайте, используя Chrome с плагином MetaMask. Есть даже видеоинструкция.
      После установки MetaMask он предложит создать кошелёк и выдаст на этот кошелёк 1 ETH в тестовой сети Ropsten. Вот на этот тестовый эфир и можно попробовать игру. Разумеется, это будут «фантики», т.к. ETH в тестовой сети никакой ценности не имеют.


    1. pash7ka
      09.04.2017 14:24

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


      1. rPman
        11.04.2017 00:11

        Я похоже продолжаю не понимать — что есть ethereum

        Я ожидал что я запущу клиент, во встроенном в нем браузере открою 'страницу' по некому стартовому адресу контракта и смогу там играть. Без необходимости в существовании какого то стороннего сервиса.

        Это все еще недостижимо?


        1. pash7ka
          11.04.2017 00:22

          Можно как вы говорите. Можно проще — никакого отдельного клиента, ваш обычный Chrome. Устанавливаете плагин MetaMask. Он делает для сайтов доступным объект web3, через который JavaScript может делать вызовы к Ethereum-кошельку встроенному в MetaMask.
          Когда вы делаете ставку скрипт на странице рулетки через web3 просить кошелёк выполнить транзакцию. Тот через JSON-RPC обращается к какой-нибудь (прописанной авторами MetaMask или настроенной вами самостоятельно) Ethereum-node, которая публикует транзакцию в сети.
          Дальше майнер, пожелав включить эту транзакцию в блок, исполняет её код. А в коде — обращения к методу placeBet() смарт-контракта рулетки.

          Если вы принципиально нехотите никаких сторонних сервисов — только вы и сеть Ethereum, вам потребуется Mist в режиме полной ноды. В его встроенном броузере открываете сайт рулетки и всё то же самое, только нода — сразу ваша, а не какая-то сторонняя.
          Или даже не открываете сайт рулетки, а просто в консоле вызываете нужные методы смарт-контракта.


          1. rPman
            11.04.2017 00:28

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

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

            В блокчейне ethereum не хранится код с интерфейсам пользователя для контрактов принципиально? или это техническое ограничение? Почему нужно открывать какой то вебсайт?


            1. pash7ka
              11.04.2017 01:23

              В блокчейне не хранится UI. Именно по этой причине нужен веб-сайт, который даёт игроку красивую картинку.
              С другой стороны, кто угодно может сделать UI к этому контракту. Вам не нравится их сайт — делайте свой.

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

              Теоретически, вы можете взять, например, весь код HTML+CSS+JS своего приложения и записать вместе с контрактом в блокчейн. Это будет довольно дорого: если я правильно понял спецификацию, за a байт придется заплатить Cmem(a) = Gmemory*a + (a^2/512) единиц газа, где Gmemory = 3. А обычная цена газа сегодня 0.00000002 ETH. Таким образом, 1Мб обойдется ~ 0.1049 ETH. Но вы можете это сделать.
              Только просматривать такой UI на сегодня момент будет нечем.


  1. YuryZakharov
    10.04.2017 16:59

    А можно контракт научить ходить на random.org?
    Там плюшки всякие, маяки, проверяемость…
    Это чтобы с ГПСЧ не морочиться и предотвратить возможность описанной потенциальной атаки?
    Ну, и по-настоящему случайные числа.


    1. pash7ka
      10.04.2017 17:11

      В EVM, виртуальной машине Ethereum, нету операций, которые позволили бы исполняемому коду сделать вызов за пределы виртуальной машины. Так что смарт-контракт может оперировать исключительно теми данными, которые находятся внутри блокчейна.

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