-
Notifications
You must be signed in to change notification settings - Fork 65
feat(contract-dev): edit random numbers guide to fit style guide and use Tolk code #2115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
a70d91a
55d9edd
c606c5f
e0cb47c
b005d42
46dba6e
b30eb35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,164 +5,144 @@ sidebarTitle: "Random numbers" | |
|
|
||
| import { Aside } from '/snippets/aside.jsx'; | ||
|
|
||
| Generating random numbers is a common task in smart contracts for applications such as gaming, NFT trait generation, and decentralized lotteries. TON provides built-in functions like [`random()`](/languages/func/stdlib#random), but their results can be predicted by validators unless additional techniques are used. | ||
|
|
||
| <Aside | ||
| type="caution" | ||
| > | ||
| Single-block randomness (such as [`randomize_lt()`](/languages/func/stdlib#randomize-lt)) is not secure against validators who can influence or predict values. For stronger guarantees, use multi-phase schemes such as commit-reveal. | ||
| </Aside> | ||
| Generating random numbers is a common smart contract task for applications such as gaming, trait generation for non-fungible tokens (NFTs), and decentralized lotteries. TON provides built-in functions like [`random.uint256()`](/tolk/features/standard-library#random-uint256), but their results can be predicted by validators unless additional techniques are used. | ||
|
|
||
| ## Why on-chain randomness is challenging | ||
|
|
||
| Computers cannot generate truly random information because they strictly follow instructions. Instead, pseudorandom number generators (PRNGs) rely on a _seed_ value to produce a sequence of pseudorandom numbers. Running the same program with the same seed will always produce identical results. | ||
| Deterministic programs use pseudorandom number generators (PRNGs) to produce sequences that appear random but are actually deterministic, based on an initial seed value. Running the same program with the same seed will always produce the same sequence of numbers. If true randomness is required, the seed must come from an external entropy source that cannot be predicted or manipulated by adversaries. | ||
|
|
||
| In TON, the seed varies for each block and is generated by validators. While this prevents regular users from predicting random values before block production, validators themselves can influence randomness in two ways: | ||
| Smart contracts on TON use pseudorandom number generators (PRNGs) seeded with values derived from the blockchain state, such as block seeds generated by validators. While this prevents regular users from predicting random values before block production, validators themselves can influence randomness in two ways: | ||
|
|
||
| 1. Generating different seeds when creating blocks | ||
| 1. Choosing which blocks to include external messages in | ||
| 1. Generate different seeds when creating blocks. | ||
| 1. Choose which blocks to include external messages in. | ||
|
|
||
| This fundamental limitation means all approaches to on-chain randomness involve trade-offs between speed, security, and decentralization guarantees. | ||
| This limitation means all approaches to on-chain randomness involve trade-offs between speed, security, and decentralization guarantees. | ||
|
|
||
| ## Approach 1: Single-block randomness with `randomize\_lt()` | ||
| ## Comparison of approaches | ||
|
|
||
| | Factor | [Single-block](#single-block-randomness) | [Block skipping](#multi-block-randomness-via-block-skipping) | [Commit-reveal](#external-randomness-via-the-commit-reveal-scheme) | | ||
| | ------------------------------------ | ---------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------ | | ||
| | Speed | Fast | Slow | Very slow | | ||
| | Implementation complexity | Low | Medium | High | | ||
| | Resistance to user manipulation | High | High | High | | ||
| | Resistance to validator manipulation | Low | Medium | High | | ||
| | Cost (gas + storage) | Low | Medium | High | | ||
|
|
||
| <Aside type="tip"> | ||
| When choosing an approach, consider the value at risk and required time-to-finality. For high-stakes applications such as lotteries with significant funds, use commit-reveal. Audit implementations through formal verification when possible. | ||
| </Aside> | ||
|
kay-is marked this conversation as resolved.
kay-is marked this conversation as resolved.
|
||
|
|
||
| ### Mechanism | ||
| ## Single-block randomness | ||
|
|
||
| The [`randomize_lt()`](/languages/func/stdlib#randomize-lt) function mixes the transaction's logical time into the random seed before generating random numbers. This provides basic entropy from blockchain state. | ||
| The [`random.initialize()`](/tolk/features/standard-library#random-initialize) function initializes the random number generator with a seed derived from the transaction's logical time. This provides basic entropy from blockchain state. | ||
|
|
||
| ### How it works | ||
|
|
||
| Call [`randomize_lt()`](/languages/func/stdlib#randomize-lt) once before using [`random()`](/languages/func/stdlib#random) or [`rand()`](/languages/func/stdlib#rand) functions. The logical time adds variability to the seed, making it harder for observers to predict the outcome without executing the transaction. | ||
| Call `random.initialize()` once before using [`random.uint256()`](/tolk/features/standard-library#random-uint256) or [`random.range()`](/tolk/features/standard-library#random-range-limit) functions. The logical time adds variability to the seed, making it harder for observers to predict the outcome without executing the transaction. | ||
|
kay-is marked this conversation as resolved.
|
||
|
|
||
| Code example: | ||
|
kay-is marked this conversation as resolved.
Outdated
|
||
|
|
||
| ```func | ||
| randomize_lt(); | ||
| ```tolk title="Tolk" | ||
| random.initialize(); | ||
| // Generates values from 0 to 99 | ||
| int random_number = rand(100); | ||
| var randomNumber = random.range(100); | ||
| ``` | ||
|
|
||
| ### Security model | ||
|
|
||
| - ✅ Safe against regular users who cannot predict logical time | ||
| - ⚠️ Vulnerable to colluding validators who can generate seeds or choose message inclusion blocks | ||
| The single-block approach prevents attacks from regular users who cannot predict logical time. | ||
|
|
||
| However, it is **vulnerable to colluding validators** who can generate seeds or choose message inclusion blocks. | ||
|
|
||
| ### Speed | ||
|
|
||
| Fast (single-block operation) | ||
| The single-block approach is fast, as it executes inside a single transaction. | ||
|
|
||
| ### Use cases | ||
|
|
||
| - Non-critical applications (gaming, cosmetic features) | ||
| - NFT trait randomization | ||
| - Scenarios where validator trust is assumed | ||
| - Non-critical applications like gaming or cosmetic features. | ||
| - NFT trait randomization. | ||
| - Scenarios where validator trust is assumed. | ||
|
kay-is marked this conversation as resolved.
|
||
|
|
||
| <Aside | ||
| type="caution" | ||
| > | ||
| Validators can predict single-block randomness. Do not use it for high-value applications such as financial lotteries or significant asset distribution. | ||
| Validators can predict single-block randomness. Do not use `random.initialize()` for high-value applications such as financial lotteries or significant asset distribution. | ||
| </Aside> | ||
|
|
||
| ## Approach 2: Block skipping | ||
|
|
||
| ### Mechanism | ||
| ## Multi-block randomness via block skipping | ||
|
|
||
| Instead of using randomness from the current block, the contract waits for several blocks to pass before using their entropy. This approach sends messages across multiple blocks, making it harder for a single validator to control the final outcome. | ||
|
|
||
| ### How it works | ||
|
|
||
| The contract initiates an operation, then waits for responses that arrive several blocks later. The random seed from future blocks—which the initiating validator does not control—determines the result. | ||
| The contract initiates an operation, then waits for responses that arrive several blocks later. The random seed from future blocks, which the initiating validator does not control, determines the result. | ||
|
kay-is marked this conversation as resolved.
|
||
|
|
||
| ### Security model | ||
|
|
||
| - ✅ Resistant to regular users | ||
| - ✅ More resistant to single validators than `randomize_lt()` | ||
| - ⚠️ Still vulnerable to determined validators who can coordinate timing across multiple blocks they generate | ||
| Block skipping is resistant to attacks from regular users and more resistant to single validators than single-block randomness. | ||
|
|
||
| However, it is still **vulnerable to determined validators** who represent 1/250 of the network, as they can still choose optimal timing to influence outcomes in blocks they generate. | ||
|
kay-is marked this conversation as resolved.
Outdated
|
||
|
|
||
| ### Speed | ||
|
|
||
| Slow (requires multiple blocks to finalize) | ||
| Block skipping is significantly slower than single-block randomness, as it requires waiting for multiple blocks to be produced and finalized. | ||
|
|
||
| ### Use cases | ||
|
|
||
| - Medium-stakes applications | ||
| - Lottery systems with moderate value | ||
| - Scenarios requiring better security than single-block randomness | ||
|
|
||
| <Aside> | ||
| While block skipping improves security over single-block randomness, it only delays the threat. A validator representing 1/250 of the network can still choose optimal timing to influence outcomes in blocks they generate. | ||
| </Aside> | ||
|
|
||
| ## Approach 3: Commit-reveal scheme | ||
| - Medium-stakes applications like lottery systems with moderate value. | ||
| - Scenarios requiring better security than single-block randomness. | ||
|
|
||
| ### Mechanism | ||
| ## External randomness via the commit-reveal scheme | ||
|
|
||
| Participants commit to secret values by publishing hashes, then reveal those values in a later phase. The final randomness is derived from combining all revealed values, ensuring no single party can determine the outcome alone. When properly implemented, this approach provides cryptographically secure randomness suitable for high-value applications. | ||
| Multiple participants commit to secret values by publishing hashes, then reveal those values in a later phase. The final randomness is derived from combining all revealed values, ensuring no single party can determine the outcome alone. When properly implemented, this approach provides cryptographically secure randomness suitable for high-value applications. | ||
|
|
||
| ### How it works | ||
|
|
||
| 1. Commit phase: Each participant generates a random number off-chain and submits its hash to the contract. | ||
| 1. Reveal phase: After all commitments are received, participants disclose their original numbers. | ||
| 1. Combination: The contract combines the revealed numbers (e.g., by XOR or sum) to produce the final random value. | ||
| 1. **Commit phase**: Each participant generates a random number off-chain and submits its hash to the contract. | ||
| 1. **Reveal phase**: After all commitments are received, participants disclose their original numbers. | ||
| 1. **Combination**: The contract combines the revealed numbers (e.g., by XOR or sum) to produce the final random value. | ||
|
Comment on lines
+100
to
+102
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bind commitments to the participant and round. Hashing only the random number is copyable and replayable; with XOR aggregation, duplicated commitments can also cancel out. The guide should require domain-separated commitments and canonical aggregation. Suggested wording-1. **Commit phase**: Each participant generates a random number off-chain and submits its hash to the contract.
+1. **Commit phase**: Each participant generates a secret off-chain and submits a domain-separated hash that binds the secret to the participant address, contract address, and round identifier.
1. **Reveal phase**: After all commitments are received, participants disclose their original numbers.
-1. **Combination**: The contract combines the revealed numbers (e.g., by XOR or sum) to produce the final random value.
+1. **Combination**: The contract validates each reveal, rejects duplicates or mismatched commitments, orders accepted reveals canonically, and hashes the transcript to produce the final random value. - On-chain verification of commitments.
+- Domain separation and replay protection for commitments.
+- Duplicate commitment handling.
- Penalty mechanisms for non-reveals or invalid reveals.
- Timeout handling for missing participants.Also applies to: 120-124 🤖 Prompt for AI Agents
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey @novusnota or @delovoyhomie, can one of you confirm this? It sounds reasonable, but my crypto skills aren't strong enough to check this without substantial effort 🫣
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| ### Security model | ||
|
|
||
| - ✅ Cryptographically secure when properly implemented | ||
| - ✅ Resistant to both user and validator manipulation; no single party can predict or influence the outcome | ||
| - ⚠️ Requires incentives and penalties to prevent participants from refusing to reveal | ||
| - ⚠️ Validators can influence timing or censor messages, but cannot determine the random value | ||
| The commit-reveal scheme is the most secure on-chain randomness approach, as it relies on cryptographic commitments and multiple independent participants. It prevents any single participant from unilaterally determining the final value. | ||
|
|
||
| Yet, **validators can still influence timing or censor messages**, but they cannot determine the final random value without colluding with participants. | ||
|
kay-is marked this conversation as resolved.
Outdated
kay-is marked this conversation as resolved.
Outdated
|
||
|
|
||
| Commit-reveal schemes require careful incentive design. Participants may refuse to reveal if the outcome is unfavorable. Use penalties or collateral to enforce honest behavior. | ||
|
|
||
| ### Speed | ||
|
|
||
| Very slow (multi-phase, multi-block process) | ||
| The commit-reveal scheme is very slow compared to other approaches, as it involves multiple phases and requires waiting for several blocks to complete the process. The time to finality can range from minutes to hours depending on the number of participants and block times. | ||
|
|
||
| ### Implementation requirements | ||
|
|
||
| - On-chain verification of commitments | ||
| - Penalty mechanisms for non-reveals or invalid reveals | ||
| - Timeout handling for missing participants | ||
| - On-chain verification of commitments. | ||
| - Penalty mechanisms for non-reveals or invalid reveals. | ||
| - Timeout handling for missing participants. | ||
|
|
||
| ### Use cases | ||
|
|
||
| - High-value applications (significant lotteries, auctions) | ||
| - Decentralized gaming with financial stakes | ||
| - Systems requiring Byzantine fault tolerance | ||
|
|
||
| <Aside | ||
| type="caution" | ||
| > | ||
| Commit-reveal schemes require careful incentive design. Participants may refuse to reveal if the outcome is unfavorable. Use penalties or collateral to enforce honest behavior. | ||
| </Aside> | ||
|
|
||
| ## Comparison of approaches | ||
|
|
||
| | Factor | `randomize_lt()` | Block skipping | Commit-reveal | | ||
| | ------------------------------------ | ---------------- | ------------------- | ------------------------------------- | | ||
| | Speed | Fast | Slow | Very slow | | ||
| | Implementation complexity | Low | Medium | High | | ||
| | Resistance to user manipulation | High | High | Cryptographically secure | | ||
| | Resistance to validator manipulation | Low | Medium | High (validators cannot predict) | | ||
| | Cost (gas + storage) | Low | Medium | High | | ||
| | Suitable for high-value applications | ❌ No | ⚠️ Use with caution | ✅ Yes (recommended for critical use) | | ||
|
|
||
| <Aside type="tip"> | ||
| When choosing an approach, consider the value at risk and required time-to-finality. For high-stakes applications such as lotteries with significant funds, use commit-reveal. Audit implementations through formal verification when possible. | ||
| </Aside> | ||
| - High-value applications like lotteries or auctions with significant funds at stake. | ||
| - Decentralized gaming with financial stakes. | ||
| - Systems requiring Byzantine fault tolerance. | ||
|
|
||
|
kay-is marked this conversation as resolved.
|
||
| ## Best practices | ||
|
|
||
| - Avoid standalone [`random()`](/languages/func/stdlib#random) calls. Validators controlling the block seed can predict the output. | ||
| - Keep randomness out of external message receivers. External messages remain vulnerable even with [`randomize_lt()`](/languages/func/stdlib#randomize-lt). | ||
| - Use hybrid or off-chain entropy for critical applications. Combine on-chain randomness with off-chain entropy or external randomness oracles when significant value is at risk. | ||
| - Test randomness behavior across different blocks. Verify that the contract behaves correctly when randomness is manipulated within validator capabilities. | ||
| - Always call `random.initialize()` before using `random.uint256()` or `random.range()` to prevent users from predicting random values. | ||
|
kay-is marked this conversation as resolved.
Outdated
|
||
| - Keep randomness out of external message receivers. External messages remain vulnerable even with `random.initialize()`. | ||
| - Use hybrid or off-chain entropy for critical applications. Combine on-chain randomness with off-chain entropy, like external randomness oracles, when significant value is at risk. | ||
| - Test randomness behavior across different blocks on testnet. Verify that contracts behave correctly when randomness is manipulated within validator capabilities. | ||
|
|
||
| ## How block random seeds work | ||
|
|
||
| Understanding the underlying mechanism helps evaluate security trade-offs. | ||
|
|
||
| ### Seed generation by validators | ||
|
|
||
| Each block's random seed is generated by the validator (or collator) creating that block. The [validator node code](https://github.com/ton-blockchain/ton/blob/f59c363ab942a5ddcacd670c97c6fbd023007799/validator/impl/collator.cpp#L1590) generates 32 random bytes using cryptographically secure primitives: | ||
| Each block's random seed is generated by the validator (or [collator](/ecosystem/nodes/cpp/mytonctrl/collator)) creating that block. The [validator node code](https://github.com/ton-blockchain/ton/blob/f59c363ab942a5ddcacd670c97c6fbd023007799/validator/impl/collator.cpp#L1590) generates 32 random bytes using cryptographically secure primitives: | ||
|
kay-is marked this conversation as resolved.
|
||
|
|
||
| ```cpp | ||
| ```cpp title="C++" | ||
| prng::rand_gen().strong_rand_bytes(rand_seed->data(), 32); | ||
| ``` | ||
|
|
||
|
|
@@ -176,19 +156,23 @@ The block seed is not used directly in contracts. Instead, it is [hashed with th | |
| contract_seed = SHA256(block_seed || contract_address) | ||
| ``` | ||
|
|
||
| This ensures different contracts in the same block receive different random seeds, preventing cross-contract randomness correlation. | ||
| This ensures each contract in the same block receives its own random seed, preventing cross-contract randomness correlation. | ||
|
|
||
| ### Random number generation | ||
|
|
||
| The [`RANDU256`](/tvm/instructions#f810-randu256) TVM instruction implements the actual random number generation. When called: | ||
| The [`RANDU256`](/tvm/instructions#f810-randu256) TVM instruction implements the actual random number generation. | ||
|
|
||
| 1. Take the current seed `r` (32 bytes) | ||
| 1. Compute `SHA512(r)` | ||
| 1. Use the first 32 bytes as the new seed | ||
| 1. Return the remaining 32 bytes as the random number | ||
| The process is as follows: | ||
|
kay-is marked this conversation as resolved.
Outdated
|
||
|
|
||
| 1. Take the current seed `r` (32 bytes). | ||
| 1. Compute a hash with `SHA512(r)`. | ||
| 1. Use the first 32 bytes as the new seed. | ||
| 1. Return the remaining 32 bytes as the random number. | ||
|
|
||
| Subsequent calls continue this chain, producing a deterministic sequence from the initial seed. | ||
|
|
||
| <Aside> | ||
| Because the random sequence is deterministic once the initial seed is known, anyone who knows both the block seed and contract address can predict all random values generated during a transaction. Regular users cannot predict these values before block production, but validators generating the block can, since they control the block seed. | ||
| <Aside | ||
| type="caution" | ||
| > | ||
| The random sequence is deterministic once the initial seed is known, so anyone who knows both the block seed and contract address can predict random values generated during a transaction. Regular users cannot predict these values before block production, but validators generating the block can, since they control the block seed. | ||
| </Aside> | ||
Uh oh!
There was an error while loading. Please reload this page.