diff --git a/docs/devdocs/Fhenix Testnet/Details/Limitations.md b/docs/devdocs/Fhenix Testnet/Details/Limitations.md index 38465482..119fb2d8 100644 --- a/docs/devdocs/Fhenix Testnet/Details/Limitations.md +++ b/docs/devdocs/Fhenix Testnet/Details/Limitations.md @@ -14,12 +14,6 @@ real sensitive data or private keys on the testnet. The current iteration of the network does not include multiple components (such as input knowledge proofs, threshold decryption, execution proofs, etc.) that are critical for the security of data and network keys. These features will be added iteratively as we move towards full release - this should be obvious, but please **do not store any valuable information on the network as long as it is in the testnet phase**. -## Randomness - -Randomness as a service is planned as a future addition. Until we can guarantee a secure source of randomness, we do not -want to make such a function available as a network service. For demos and development that require a source of randomness, we encourage -the use of external oracles, or usage of a [mock random number generator](../../Writing%20Smart%20Contracts/Useful-Tips.md#randomness). - ## Gas Costs All gas costs are subject to change, and are being evaluated for optimization. The current gas costs are not final, and may change. diff --git a/docs/devdocs/Writing Smart Contracts/Randomness.md b/docs/devdocs/Writing Smart Contracts/Randomness.md new file mode 100644 index 00000000..cedf5465 --- /dev/null +++ b/docs/devdocs/Writing Smart Contracts/Randomness.md @@ -0,0 +1,116 @@ +# Randomness + +Randomness was introduced in the Nitrogen testnet. +Contracts in Fhenix can get random numbers by calling one of the randomness functions in `FHE.sol`. +These functions are: +```solidity +import { + FHE, euint8, euint16, euint32, euint64, euint128, euint256 +} from "@fhenixprotocol/contracts/FHE.sol"; + +euint8 randomValue = FHE.randomEuint8(); +euint16 randomValue = FHE.randomEuint16(); +euint32 randomValue = FHE.randomEuint32(); +euint64 randomValue = FHE.randomEuint64(); +euint128 randomValue = FHE.randomEuint128(); +euint256 randomValue = FHE.randomEuint256(); +``` + +Note that the random values are returned encrypted. +This is a fundamental quality of randomness generation, because if the returned value +was plaintext, then it would be possible to simulate the execution and predict the random value. + +### Best practice: Ensure caller is not a Contract +When acting upon the resulting random numbers, it is important to keep the following +scenario in mind. + +Suppose we have a simple game contract that you can send funds to, and with a probability of `P=0.5` (or, 50% chance), +you will receive double the funds back. + +```solidity +contract RandomLucky { + function play() external payable { + require(msg.value > 0, "You need to send some FHE"); + + // Generate a random encrypted number + euint8 outcome = FHE.randomEuint8(); + uint8 outcomeDecrypted = outcome.decrypt(); + + // If the outcome is even, send double the value back to the sender + if (outcomeDecrypted % 2 == 0) { + uint prize = msg.value * 2; + require(address(this).balance >= prize, "Contract does not have enough balance"); + payable(msg.sender).transfer(prize); + } + // If the outcome is odd, the contract keeps the value + } + + // Fallback function to receive FHE + receive() external payable {} +} +``` +An adversary could could call the randomness consumer function, check the result of the random, +and revert the transaction if that result were not favorable. + +In this case: +```solidity +contract Adversary { + RandomLucky game; + + constructor(address gameAddress) { + game = RandomLucky(gameAddress); + } + + // Function to attack the RandomLucky contract + function attack() public payable { + require(msg.value > 0, "Must send some FHE to attack"); + + // Store the initial balance of the contract + uint256 initialBalance = address(this).balance; + + // Call the play function of the RandomLucky contract + game.play{value: msg.value}(); + + // Check if the balance did not increase + if (address(this).balance <= initialBalance) { + revert("Did not win, reverting transaction"); + } + } +} +``` + +To prevent this kind of attacks, it is recommended to not allow contracts +to call functions that act upon random numbers, like so: +```solidity +modifier callerIsUser() { + require(tx.origin == msg.sender, "The caller is another contract"); + _; +} + +function play() callerIsUser { + ... +} +``` + +:::danger[Warning] +### Randomness in View functions +Randomness is guaranteed to be unique for each transaction, but not for each `eth_call`. +Specifically, two eth_calls to the same contract, on the same block may receive the same random value (more on this below). + +::: + +:::info[How does it work?] + +Random generation takes as input a seed, and returns a random number which is unique for each seed and key sets. + +To cause each random to be different for each transaction, the seed is created from a) the contract address, +b) the transaction hash, and c) a counter that gets incremented each transaction. +```bash +seed = hash(contract_address, tx_hash, counter) +``` +For eth calls, which don't have a tx_hash nor can use a counter, we use the block hash instead, that's why two quick subsequent +calls to the same contract may return the same random number. +```bash +seed = hash(contract_address, block_hash) +``` +::: diff --git a/docs/devdocs/Writing Smart Contracts/Types-and-Operators.md b/docs/devdocs/Writing Smart Contracts/Types-and-Operators.md index 4a867fa3..fc38d9f6 100644 --- a/docs/devdocs/Writing Smart Contracts/Types-and-Operators.md +++ b/docs/devdocs/Writing Smart Contracts/Types-and-Operators.md @@ -104,32 +104,32 @@ Please refer to the table below for a comprehensive list of supported operations Note that all functions are supported in both direct function calls and library bindings. However, operator overloading is only supported for the operations listed in the table (solidity please support operator overloading for boolean return types!). - -| Name | FHE.sol function | Operator | euint8 | euint16 | euint32 | euint64 | euint128 | euint256 | ebool | eaddress | -|-----------------------|-------------------|:---------:|:--------:|:--------:|:--------:|:---------:|:-----------:|:-------------:|:--------:|:-----------:| -| Addition | `add` | `+` | | | | | | | n/a | n/a | -| Subtraction | `sub` | `-` | | | | | | | n/a | n/a | -| Multiplication | `mul` | `*` | | | | | | | n/a | n/a | -| Bitwise And | `and` | `&` | | | | | | | | n/a | -| Bitwise Or | `or` | `\|` | | | | | | | | n/a | -| Bitwise Xor | `xor` | `^` | | | | | | | | n/a | -| Division | `div` | `/` | | | | | | | n/a | n/a | -| Remainder | `rem` | `%` | | | | | | | n/a | n/a | -| Shift Right | `shr` | n/a | | | | | | | n/a | n/a | -| Shift Left | `shl` | n/a | | | | | | | n/a | n/a | -| Equal | `eq` | n/a | | | | | | | | | -| Not equal | `ne` | n/a | | | | | | | | | -| Greater than or equal | `gte` | n/a | | | | | | | n/a | n/a | -| Greater than | `gt` | n/a | | | | | | | n/a | n/a | -| Less than or equal | `lte` | n/a | | | | | | | n/a | n/a | -| Less than | `lt` | n/a | | | | | | | n/a | n/a | -| Min | `min` | n/a | | | | | | | n/a | n/a | -| Max | `max` | n/a | | | | | | | n/a | n/a | -| Not | `not` | n/a | | | | | | | | n/a | -| Select | `select` | n/a | | | | | | | | | -| Require | `req` | n/a | | | | | | | | | -| Decrypt | `decrypt` | n/a | | | | | | | | | -| Seal Output | `sealOutput` | n/a | | | | | | | | | +| Name | FHE.sol function | Operator | euint8 | euint16 | euint32 | euint64 | euint128 | euint256 | ebool | eaddress | +|-----------------------|------------------|:---------:|:--------:|:--------:|:--------:|:---------:|:-----------:|:-------------:|:--------:|:-----------:| +| Addition | `add` | `+` | | | | | | | n/a | n/a | +| Subtraction | `sub` | `-` | | | | | | | n/a | n/a | +| Multiplication | `mul` | `*` | | | | | | | n/a | n/a | +| Bitwise And | `and` | `&` | | | | | | | | n/a | +| Bitwise Or | `or` | `\|` | | | | | | | | n/a | +| Bitwise Xor | `xor` | `^` | | | | | | | | n/a | +| Division | `div` | `/` | | | | | | | n/a | n/a | +| Remainder | `rem` | `%` | | | | | | | n/a | n/a | +| Shift Right | `shr` | n/a | | | | | | | n/a | n/a | +| Shift Left | `shl` | n/a | | | | | | | n/a | n/a | +| Equal | `eq` | n/a | | | | | | | | | +| Not equal | `ne` | n/a | | | | | | | | | +| Greater than or equal | `gte` | n/a | | | | | | | n/a | n/a | +| Greater than | `gt` | n/a | | | | | | | n/a | n/a | +| Less than or equal | `lte` | n/a | | | | | | | n/a | n/a | +| Less than | `lt` | n/a | | | | | | | n/a | n/a | +| Min | `min` | n/a | | | | | | | n/a | n/a | +| Max | `max` | n/a | | | | | | | n/a | n/a | +| Not | `not` | n/a | | | | | | | | n/a | +| Select | `select` | n/a | | | | | | | | | +| Require | `req` | n/a | | | | | | | | | +| Decrypt | `decrypt` | n/a | | | | | | | | | +| Seal Output | `sealOutput` | n/a | | | | | | | | | +| Randomness | `randomEuintX` | n/a | | | | | | | | | :::danger[Caveat] At the moment it is not possible to do `ebool result = (lhs == rhs)` and others that return a boolean result. This is because FHE.sol expects a `ebool`, while Solidity only allows overloading to return a regular boolean.