RSA-рандом на блокчейні

Є проблема – складно згенерувати випадкове число у децентралізованій мережі. Чи не всі блокчейни вже з цим зіткнулися. Адже в мережах, де немає довіри між користувачами, створення незаперечної випадкової кількості вирішує безліч завдань.

У статті розповідаємо, як удалося вирішити проблему на прикладі ігор. Першою з них стала Waves Xmas Tree. Для розробки нам знадобився генератор випадкових чисел.

RSA-рандом на блокчейні

Спочатку ми планували генерувати число на основі інформації з блокчейну. Однак потім стало ясно: число можуть підтасувати, а значить, рішення не підходить.

Ми вигадали обхідний шлях: використовувати схему «комміт-розкриття». Сервер "загадував" число від 1 до 5, додавав до нього "сіль", а потім хешував результат за допомогою функції Keccak. Сервер заздалегідь деплоїв смарт-контракт із збереженим числом. Виходить, гра зводилася до того, що користувач вгадував число, приховане хеш.

Гравець робив ставку, а сервер відправляв загадане число і сіль на смарт-контракт. Простим язиком, розкривав карти. Після цього сервер звіряв цифри та вирішував, переміг користувач або програв.

Якщо сервер не надсилав число або сіль для перевірки, користувач перемагав. У цьому випадку для кожної гри потрібно було заздалегідь деплоїти смарт-контракт і закладати в нього потенційний виграш. Виявилося, це незручно, довго та дорого. На той момент іншого безпечного рішення не було.

Нещодавно команда Tradisys передбачила додати до протоколу Waves функцію rsaVerify(). Вона перевіряє валідність RSA-підпису на підставі публічного та приватного ключа. У результаті функцію було додано.

Ми розробили три ігри: Каже Ролик, Фліп монети и Ride On Waves. У кожній реалізовано технологію випадкового числа. Розберемося, як це працює.

RSA-рандом на блокчейні

Розглянемо генерацію випадкового числа з прикладу Ride on Waves. Смарт-контракт можна знайти тут.

Перейдіть у вкладку Script і виберіть Decompiled. Побачте код смарт-контракту (він скрипт).

RSA-рандом на блокчейні

Код смарт-контракту містить набір функцій. Ті, що позначені як @Callable можуть запускатися за допомогою Invocation-транзакцій. Нас цікавлять дві функції: ставка и вилучати:

  • func bet (playerChoice)
  • func withdraw (gameId,rsaSign)

1. Користувач вибирає довжину відрізка та розмір ставки.

RSA-рандом на блокчейні

2. Клієнт формує bet-функцію. Для зображення вище це буде bet («50»).

3. Клієнт відправляє транзакцію Invocation на адресу смарт-контракту (broadcast InvocationTx). Транзакція як Сall-параметр містить функцію bet. Це означає, що Invocation транзакція запускає виконання bet-функції (choice: String) на смарт-контракті.

RSA-рандом на блокчейні

4. Розглянемо bet-функцію:

@Callable(i)
func bet (playerChoice) = {
    let newGameNum = IncrementGameNum()
    let gameId = toBase58String(i.transactionId)
    let pmt = extract(i.payment)
    let betNotInWaves = isDefined(pmt.assetId)
    let feeNotInWaves = isDefined(pmt.assetId)
    let winAmt = ValidateBetAndDefineWinAmt(pmt.amount, playerChoice)
    let txIdUsed = isDefined(getString(this, gameId))
    if (betNotInWaves)
        then throw ("Bet amount must be in Waves")
        else if (feeNotInWaves)
            then throw ("Transaction's fee must be in Waves")
            else if (txIdUsed)
                then throw ("Passed txId had been used before. Game aborted.")
                else {
                    let playerPubKey58 = toBase58String(i.callerPublicKey)
                    let gameDataStr = FormatGameDataStr(STATESUBMITTED, playerChoice, playerPubKey58, height, winAmt, "")
                    ScriptResult(WriteSet(cons(DataEntry(RESERVATIONKEY, ValidateAndIncreaseReservedAmt(winAmt)), cons(DataEntry(GAMESCOUNTERKEY, newGameNum), cons(DataEntry(gameId, gameDataStr), nil)))), TransferSet(cons(ScriptTransfer(SERVER, COMMISSION, unit), nil)))
                    }
    }

Функція записує в стейт смарт-контракту нову гру. А саме:

  • Унікальний ідентифікатор нової гри (Game id)
  • Game state = SUBMITTED
  • Вибір гравця (довжина відрізка 50)
  • Публічний ключ
  • Потенційний виграш (залежить від ставки гравця)

RSA-рандом на блокчейні

Так виглядає запис даних у блокчейні (ключ-значення):

{
    "type": "string",
    "value": "03WON_0283_448t8Jn9P3717UnXFEVD5VWjfeGE5gBNeWg58H2aJeQEgJ_06574069_09116020000_0229",
    "key": "2GKTX6NLTgUrE4iy9HtpSSHpZ3G8W4cMfdjyvvnc21dx"
  }

"Ключ" (key) - game id нової гри. Інші дані містяться в рядку поля значення (value). Ці записи зберігаються у вкладці дані смарт-контракту:

RSA-рандом на блокчейні

RSA-рандом на блокчейні

5. Сервер дивиться на смарт-контракт і знаходить відправлену транзакцію (нову гру) за допомогою Api блокчейна. Game id нової гри вже записано в блокчейні, а значить змінити або вплинути на неї вже не можна

6. Сервер формує звиразною функцією (gameId, rsaSign). Наприклад, таку:

withdraw ("FwsuaaShC6DMWdSWQ5osGWtYkVbTEZrsnxqDbVx5oUpq", "base64:Gy69dKdmXUEsAmUrpoWxDLTQOGj5/qO8COA+QjyPVYTAjxXYvEESJbSiCSBRRCOAliqCWwaS161nWqoTL/TltiIvw3nKyd4RJIBNSIgEWGM1tEtNwwnRwSVHs7ToNfZ2Dvk/GgPUqLFDSjnRQpTHdHUPj9mQ8erWw0r6cJXrzfcagKg3yY/0wJ6AyIrflR35mUCK4cO7KumdvC9Mx0hr/ojlHhN732nuG8ps4CUlRw3CkNjNIajBUlyKQwpBKmmiy3yJa/QM5PLxqdppmfFS9y0sxgSlfLOgZ51xRDYuS8NViOA7c1JssH48ZtDbBT5yqzRJXs3RnmZcMDr/q0x6Bg==")

7. Сервер відправляє транзакцію Invocation на смарт-контракт (broadcast InvocationTx). Транзакція містить виклик сформованої з draw-функціями (gameId, rsaSign):

RSA-рандом на блокчейні

Функція містить game id нової гри та результат RSA-підпису унікального ідентифікатора приватним ключем. Результат підпису незмінний.

Що це означає?

Беремо те саме значення (game id) і застосовуємо до нього метод RSA-підпису. Будемо завжди отримувати той самий результат. Так працює RSA-алгоритм. Не можна маніпулювати фінальним числом, оскільки game id та результат застосування RSA не відомий. Підбирати число також безглуздо.

8. Блокчейн приймає транзакцію. Вона запускає з draw-функцією (gameId, rsaSign)

9. Усередині withdraw-функції відбувається вивіз GenerateRandInt-функції (GameId, rsaSign). Це і є генератор випадкових чисел

# @return 1 ... 100
func GenerateRandInt (gameId,rsaSign) = {
   	# verify RSA signature to proof random
    let rsaSigValid = rsaVerify (SHA256, toBytes(gameId), rsaSign, RSAPUBLIC)
    if (rsaSigValid)
        then {
            let rand = (toInt(sha256(rsaSign)) % 100)
            if ((0 > rand))
                then ((-1 * rand) + 1)
                else (rand + 1)
            }
        else throw ("Invalid RSA signature")
    }

рядок - І є випадкове число.

Спочатку береться рядок, який є результатом RSA-підпису game id приватним ключем (rsaSign). Потім хешується за допомогою SHA-256 (sha256(rsaSign)).

Ми не можемо передбачити результат підпису та подальшого хешування. Тому неможливо вплинути на створення випадкового числа. Щоб отримати число у певному діапазоні (наприклад, від 1 до 100), застосовується функція перетворення toInt та %100 (аналог модуль).

На початку статті ми згадували про функцію rsaVerify(), яка дозволяє перевірити валідність RSA-підпису приватним ключем за публічним. Ось частина GenerateRandInt (gameId,rsaSign):

rsaVerify (SHA256, toBytes(gameId), rsaSign, RSAPUBLIC)

На вхід передається публічний ключ RSAPUBLIC та рядок rsaSign. Підпис перевіряється на валідність. Число генерується у разі успішної перевірки. У протилежному випадку система вважає, що підпис не валідний (Invalid RSA signature).

Сервер повинен підписати game id гри приватним ключем та відправити валідну Rsa-підпис протягом 2880 блоків. Параметр налаштовується при депло смарт-контракту. Якщо за відведений час нічого не відбувається, користувач виграє. У цьому випадку приз потрібно надіслати на свою адресу самостійно. Виходить, серверу "не вигідно обманювати", адже це веде до програшу. Нижче – приклад.

RSA-рандом на блокчейні

Користувач грає в Каже Ролик. Вибрав 2 із 6 граней кубика, ставка – 14 WAVES. Якщо сервер не надішле валідний підпис RSA на смарт-контракт протягом встановленого часу (2880 блоків), користувач забере 34.44 WAVES.

Для генерації чисел в іграх ми використовуємо оракул - зовнішню, не блокчейнову систему. Сервер здійснює RSA-підпис game id. Смарт-контракт перевіряє валідність підпису та визначає переможця. Якщо сервер не надіслав нічого, то користувач автоматично перемагає.

Це є чесний метод генерації, адже маніпуляція технічно неможлива. Всі ігри Tradisys працюють на основі описаного алгоритму. Так працюють ігри на блокчейні. Все прозоро та піддається перевірці. Аналогів такої системи немає в жодному іншому блокчейні. Це чесний рандом.

Джерело: habr.com

Додати коментар або відгук