diff --git a/images/entropy-1.png b/images/entropy-1.png deleted file mode 100644 index 2227f9ff..00000000 Binary files a/images/entropy-1.png and /dev/null differ diff --git a/images/entropy-2.png b/images/entropy-2.png deleted file mode 100644 index 7b99a1ef..00000000 Binary files a/images/entropy-2.png and /dev/null differ diff --git a/pages/entropy/create-your-first-entropy-app.mdx b/pages/entropy/create-your-first-entropy-app.mdx index 143245c7..03163931 100644 --- a/pages/entropy/create-your-first-entropy-app.mdx +++ b/pages/entropy/create-your-first-entropy-app.mdx @@ -40,7 +40,7 @@ Create a new file `CoinFlip.sol` in `contracts/src` directory and add the follow ```solidity copy // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol"; +import "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol"; import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol"; contract CoinFlip is IEntropyConsumer { @@ -48,11 +48,9 @@ contract CoinFlip is IEntropyConsumer { event FlipResult(uint64 sequenceNumber, bool isHeads); IEntropy entropy; - address provider; - constructor(address _entropy, address _provider) { + constructor(address _entropy) { entropy = IEntropy(_entropy); - provider = _provider; } // This method is required by the IEntropyConsumer interface @@ -63,7 +61,7 @@ contract CoinFlip is IEntropyConsumer { ``` -The code implements a`CoinFlip` contract which inherits the `IEntropyConsumer` interface. We have also defined some events, properties and a constructor to instantiate the contract. One of the properties is of type `IEntropy` which is an interface imported from the Entropy SDK. The constructor also takes a `provider` argument. We will see how to populate these later. +The code implements a`CoinFlip` contract which inherits the `IEntropyConsumer` interface. We have also defined some events, properties and a constructor to instantiate the contract. One of the properties is of type `IEntropy` which is an interface imported from the Entropy SDK. ### Request a coin flip @@ -73,17 +71,14 @@ Copy the following code into `CoinFlip.sol`. contract CoinFlip { // ... prior code omitted - function request(bytes32 userRandomNumber) external payable { + function request() external payable { // get the required fee - uint128 requestFee = entropy.getFee(provider); + uint128 requestFee = entropy.getFeeV2(); // check if the user has sent enough fees if (msg.value < requestFee) revert("not enough fees"); // pay the fees and request a random number from entropy - uint64 sequenceNumber = entropy.requestWithCallback{ value: requestFee }( - provider, - userRandomNumber - ); + uint64 sequenceNumber = entropy.requestV2{ value: requestFee }(); // emit event emit FlipRequested(sequenceNumber); @@ -92,7 +87,7 @@ contract CoinFlip { ``` -Users will invoke the `request` method to initiate a coin flip with a request fee and passes in a `userRandomNumber` argument — we’ll see how to generate this later. The method first retrieves the fee required to request a random number from Entropy. It then include the fee in the `requestWithCallback` method call to entropy. Finally, the method emits a `FlipRequested` event with a `sequenceNumber`. This event is also defined in the code snippet above. +Users will invoke the `request` method to initiate a coin flip, paying a fee in the process. The method first retrieves the fee required to request a random number from Entropy. It then includes the fee in the `requestV2` method call to Entropy. Finally, the method emits a `FlipRequested` event with a `sequenceNumber`. This event is also defined in the code snippet above. ### Handle the callback @@ -119,7 +114,7 @@ contract CoinFlip { ``` Implement `entropyCallback` method which is required by the `IEntropyConsumer` Interface. Entropy calls back this method to fulfill a request. Entropy will call back this -method with the `sequenceNumber` of the request, the `providerAddress` from which the random number was requested and the generated `randomNumber`. +method with the `sequenceNumber` of the request, the `_providerAddress` from which the random number was requested and the generated `randomNumber`. Finally, the method emits a `FlipResult` event with the result of the flip. Yay! you have successfully implemented a coin flip contract. @@ -158,7 +153,6 @@ The final step before deploying is to get the arguments for the contract's const ```bash copy export ENTROPY_ADDRESS=0x4821932D0CDd71225A6d914706A621e0389D7061 -export PROVIDER_ADDRESS=0x6CC14824Ea2918f5De5C2f75A9Da968ad4BD6344 ``` Finally, let's deploy the contracts. Run the following command: @@ -167,7 +161,7 @@ Finally, let's deploy the contracts. Run the following command: forge create src/CoinFlip.sol:CoinFlip \ --private-key $PRIVATE_KEY \ --rpc-url $RPC_URL \ ---constructor-args $ENTROPY_ADDRESS $PROVIDER_ADDRESS +--constructor-args $ENTROPY_ADDRESS ``` You should see an output similar to: @@ -206,7 +200,7 @@ Create a `script.js` file in `app` and add the following code to the script. ```javascript copy const { Web3 } = require("web3"); const CoinFlipAbi = require("../contracts/out/CoinFlip.sol/CoinFlip.json"); -const EntropyAbi = require("@pythnetwork/entropy-sdk-solidity/abis/IEntropy.json"); +const EntropyAbi = require("@pythnetwork/entropy-sdk-solidity/abis/IEntropyV2.json"); async function main() { const web3 = new Web3(process.env["RPC_URL"]); @@ -240,14 +234,11 @@ async main() { // Request a random number - // Generate user random number - const userRandomNumber = web3.utils.randomHex(32); - - const fee = await entropyContract.methods.getFee(process.env["PROVIDER_ADDRESS"]).call() + const fee = await entropyContract.methods.getFeeV2().call() console.log(`fee : ${fee}`); const requestReceipt = await coinFlipContract.methods - .request(userRandomNumber) + .request() .send({ value: fee, from: address, diff --git a/pages/entropy/current-fees.mdx b/pages/entropy/current-fees.mdx index 2dbc66af..7f8377c8 100644 --- a/pages/entropy/current-fees.mdx +++ b/pages/entropy/current-fees.mdx @@ -8,7 +8,7 @@ Note that the fees shown below will vary over time with prevailing gas prices on ## Mainnet - The fees for mainnet are dynamically set. Always use the onchain method `entropy.getFee(entropyProvider){:solidity}` to get the current fee. + The fees for mainnet are dynamically set. Always use the on-chain method `entropy.getFee(entropyProvider){:solidity}` to get the current fee. **Quick Debug Tool**: Use the [Entropy Debugger](https://entropy-debugger.pyth.network/) to quickly diagnose and diff --git a/pages/entropy/generate-random-numbers/evm.mdx b/pages/entropy/generate-random-numbers/evm.mdx index a6ec0808..b834157a 100644 --- a/pages/entropy/generate-random-numbers/evm.mdx +++ b/pages/entropy/generate-random-numbers/evm.mdx @@ -36,114 +36,77 @@ Then add the following line to your `remappings.txt` : The Solidity SDK exports two interfaces: - [`IEntropyConsumer`](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/IEntropyConsumer.sol) - The interface that your contract should implement. It makes sure that your contract is compliant with the Entropy contract. -- [`IEntropy`](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol) - The interface to interact with the Entropy contract. +- [`IEntropyV2`](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/IEntropyV2.sol) - The interface to interact with the Entropy contract. You will need the address of an Entropy contract on your blockchain. Consult the current [Entropy contract addresses](../contract-addresses) to find the address on your chain. - Once you have a contract address, instantiate an `IEntropy` contract in your solidity contract: + Once you have a contract address, instantiate an `IEntropyV2` contract in your solidity contract: ```solidity copy import { IEntropyConsumer } from "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol"; -import { IEntropy } from "@pythnetwork/entropy-sdk-solidity/IEntropy.sol"; +import { IEntropyV2 } from "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol"; // @param entropyAddress The address of the entropy contract. contract YourContract is IEntropyConsumer { - IEntropy public entropy; + IEntropyV2 public entropy; constructor(address entropyAddress) { - entropy = IEntropy(entropyAddress); + entropy = IEntropyV2(entropyAddress); } } ``` - -Entropy also requires selecting a **randomness provider**. The randomness provider is a third-party -who participates in the generation process. Each provider is identified by an address and hosts -a keeper service for fullfilling requests. - -The simplest way to choose a provider is to use the [default provider](../contract-addresses). -The default provider for each contract and their corresponding URI is also listed in the -[Entropy contract addresses](../contract-addresses). - - - -You can also get the default provider's address by calling the [`getDefaultProvider`](https://github.com/pyth-network/pyth-crosschain/blob/f8ebeb6af31d98f94ce73edade6da2ebab7b2456/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol#L94) method: - -```solidity copy -address provider = entropy.getDefaultProvider(); -``` - ## Usage To generate a random number, follow these steps. -### 1. Generate a random number - -Generate a 32-byte random number on the client side. +### 1. Request a number from Entropy - - - ```javascript - const userRandomNumber = web3.utils.randomHex(32); - ``` - - - - ```javascript - const userRandomNumber = ethers.utils.randomBytes(32); - ``` - - +# TODO: fix links and events -### 2. Request a number from Entropy +Invoke the [`requestV2`](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol#L83) method of the `IEntropyV2` contract. +The `requestV2` method requires paying a fee in native gas tokens which is configured per-provider. -Invoke the [`requestWithCallback`](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol#L83) method of the `IEntropy` contract. -The `requestWithCallback` method requires paying a fee in native gas tokens which is configured per-provider. - -The fees differs for every chain and can be found at the [Current Fees](../current-fees) page. \ -You can use the onchain method [`getFee`](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol#L101) to calculate the fee for the default provider and send it as the value of the `requestWithCallback` call: +The fees differs for every chain and also varies over time depending on the chain's current gas price. +The current value for each chain can be found on the [Current Fees](../current-fees) page. +However, you should use the on-chain method [`getFeeV2`](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol#L101) to compute the required fee and send it as the value of the `requestV2` call: ```solidity copy -function requestRandomNumber(bytes32 userRandomNumber) external payable { - uint256 fee = entropy.getFee(entropyProvider); +function requestRandomNumber() external payable { + uint256 fee = entropy.getFeeV2(); - uint64 sequenceNumber = entropy.requestWithCallback{ value: fee }( - entropyProvider, - userRandomNumber - ); + uint64 sequenceNumber = entropy.requestV2{ value: fee }(); } ``` This method returns a sequence number and emits a [`RequestedWithCallback`](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/EntropyEvents.sol#L10) event. You can store this sequence number to identify the request in next step. -### 3. Implement callback for Entropy +Note that there are several variants of `requestV2` that allow the caller to configure the provider fulfilling the request and the gas limit for the callback. +Please see the method documentation in the IEntropyV2 interface. TODO: link + +### 2. Implement the Entropy callback ```solidity {28-42} copy pragma solidity ^0.8.0; import { IEntropyConsumer } from "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol"; -import { IEntropy } from "@pythnetwork/entropy-sdk-solidity/IEntropy.sol"; +import { IEntropyV2 } from "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol"; contract YourContract is IEntropyConsumer { - IEntropy entropy; + IEntropyV2 entropy; // @param entropyAddress The address of the entropy contract. constructor(address entropyAddress) { - entropy = IEntropy(entropyAddress); + entropy = IEntropyV2(entropyAddress); } - // @param userRandomNumber The random number generated by the user. - function requestRandomNumber(bytes32 userRandomNumber) external payable { - // Get the default provider and the fee for the request - address entropyProvider = entropy.getDefaultProvider(); - uint256 fee = entropy.getFee(entropyProvider); + function requestRandomNumber() external payable { + // Get the fee for the request + uint256 fee = entropy.getFeeV2(); // Request the random number with the callback - uint64 sequenceNumber = entropy.requestWithCallback{ value: fee }( - entropyProvider, - userRandomNumber - ); + uint64 sequenceNumber = entropy.requestV2{ value: fee }(); // Store the sequence number to identify the callback request } @@ -154,6 +117,7 @@ contract YourContract is IEntropyConsumer { // This method **must** be implemented on the same contract that requested the random number. // This method should **never** return an error -- if it returns an error, then the keeper will not be able to invoke the callback. // If you are having problems receiving the callback, the most likely cause is that the callback is erroring. + // TODO: point to debugger // See the callback debugging guide here to identify the error https://docs.pyth.network/entropy/debug-callback-failures function entropyCallback( uint64 sequenceNumber, @@ -200,3 +164,20 @@ Check the [Current Fees](../current-fees) to find the current fee for each provi ### Best Practices Check out the [Best Practices](../best-practices) guide for tips to limit gas usage, or generate multiple random numbers in a single transaction. + + +Entropy also requires selecting a **randomness provider**. The randomness provider is a third-party +who participates in the generation process. Each provider is identified by an address and hosts +a keeper service for fullfilling requests. + +The simplest way to choose a provider is to use the [default provider](../contract-addresses). +The default provider for each contract and their corresponding URI is also listed in the +[Entropy contract addresses](../contract-addresses). + + + +You can also get the default provider's address by calling the [`getDefaultProvider`](https://github.com/pyth-network/pyth-crosschain/blob/f8ebeb6af31d98f94ce73edade6da2ebab7b2456/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol#L94) method: + +```solidity copy +address provider = entropy.getDefaultProvider(); +``` diff --git a/pages/entropy/protocol-design.mdx b/pages/entropy/protocol-design.mdx index b8f52a19..98aa3b82 100644 --- a/pages/entropy/protocol-design.mdx +++ b/pages/entropy/protocol-design.mdx @@ -1,21 +1,20 @@ # Protocol Design -The Entropy protocol is an extension of a classical commit/reveal protocol. -The original version has the following steps: +The Entropy protocol implements a secure 2-party random number generation procedure. The protocol +is an extension of a simple commit/reveal protocol. The original version has the following steps: -1. Two parties A and B each draw secret random numbers, $x_A$ and $x_B$. -2. A and B hash their random numbers and share the hashes, $h_A = \mathrm{hash}(x_A)$ and $h_B = \mathrm{hash}(x_B)$ -3. A and B reveal $x_A$ and $x_B$ -4. Both parties verify that $\mathrm{hash}(x_A) = h_A$ and $\mathrm{hash}(x_B) = h_B$ -5. The random number $r = \mathrm{hash}(x_A, x_B)$ +1. Two parties A and B each randomly sample secret contributions to the random number, $x_A$ and $x_B$. +2. A commits to their number by sharing $h_A = \mathrm{hash}(x_A)$ +3. B reveals $x_B$ +4. A reveals $x_A$ +5. B verifies that $\mathrm{hash}(x_{A}) == h_A$ +6. The random number $r = \mathrm{hash}(x_A, x_B)$ This protocol has the property that the result is random as long as either A or B are honest. -Thus, neither party needs to trust the other -- as long as they are themselves honest, they can +Honesty means that (1) they draw their value at random, and (2) for A, they keep $x_A$ a secret until +step 4. Thus, neither party needs to trust the other -- as long as they are themselves honest, they can ensure that the result $r$ is random. -The diagram below shows the protocol flow: -![Entropy Protocol Flow](images/entropy-1.png) - Entropy implements a version of this protocol that is optimized for on-chain usage. The key difference is that one of the participants (the provider) commits to a sequence of random numbers up-front using a hash chain. Users of the protocol then simply grab the next random number in the sequence. @@ -28,28 +27,23 @@ up-front using a hash chain. Users of the protocol then simply grab the next ran The provider commits to $x_0$ by posting it to the Entropy contract. Each random number in the sequence can then be verified against the previous one in the sequence by hashing it, i.e., $\mathrm{hash}(x_i) = x_{i - 1}$ -Pyth Entropy uses automatic callbacks to simplify the flow: - -- **Request**: To produce a random number, the following steps occur. - -1. The user U draws a random number $x_U$, and submits it to the contract. The contract generates the hash $h_U = \mathrm{hash}(x_U)$ and records both $x_U$ and $h_U$. The contract uses [`constructUserCommitment`](https://github.com/pyth-network/pyth-crosschain/blob/7bccde484f01c19844b7105d63df207a24018957/target_chains/ethereum/contracts/contracts/entropy/Entropy.sol#L628-L632) to generate the user's commitment. -2. The contract [remembers $h_U$ and assigns it an incrementing **sequence number $i$**](https://github.com/pyth-network/pyth-crosschain/blob/7bccde484f01c19844b7105d63df207a24018957/target_chains/ethereum/contracts/contracts/entropy/Entropy.sol#L232-L246), representing which - of the provider's random numbers the user will receive. $x_U$ is recorded in the [event logs](https://github.com/pyth-network/pyth-crosschain/blob/7bccde484f01c19844b7105d63df207a24018957/target_chains/ethereum/contracts/contracts/entropy/Entropy.sol#L300-L306). -3. After sufficient block confirmations, the provider submits a reveal transaction with $x_i$ and $x_U$ to the contract. -4. The contract verifies $\mathrm{hash}(x_U) = h_U$ and $\mathrm{hash}(x_i) = x_{i-1}$ to prove that $x_i$ is the $i$'th random number. -5. If both of the above conditions are satisfied, - the random number $r = \mathrm{hash}(x_i, x_U)$ is generated and a callback is made to the requesting contract. - -In this flow, providers can refuse revealing $x_i$ if the final random number $r$ is not in their favor, or -they may be able to access $x_U$ before on-chain submission (e.g. via mempool) and rotate their commitments to influence the random number $r$. -Of course, both of these behaviors are detectable and protocols can blacklist providers that exhibit them. +**Request**: To produce a random number, the following steps occur. -This protocol has the same security properties as the 2-party randomness protocol above: as long as either -the provider or user is honest, the number $r$ is random. +1. The user randomly samples their contribution $x_U$ and submits it to the contract. + (Users may also run this step using an on-chain PRNG if they trust the validator to not collude with the provider.) +2. The contract remembers $x_U$ and assigns it an incrementing sequence number $i$, representing which + of the provider's random numbers the user will receive. +3. After sufficient block confirmations, the provider submits a transaction to the contract revealing their contribution $x_i$ to the contract. +4. The contract verifies $\mathrm{hash}(x_i) == x_{i-1}$ to prove that $x_i$ is the $i$'th random number. + The contract stores x_i as the i'th random number to reuse for future verifications. +5. If the condition above is satisfied, the random number $r = \mathrm{hash}(x_i, x_U)$. +6. The contract submits a callback to the calling contract with the random number $r$. -The diagram below shows the user's interaction with the entropy contract to generate a random number: -![Entropy Contract Flow](images/entropy-2.png) +This flow is secure as long as several trust assumptions hold: -Note that providers need to be careful to ensure their off-chain service isn't compromised to reveal the random numbers -- if this occurs, then users will be able to influence the random number $r$. +- Providers are trusted to reveal their random number $x_i$ regardless of what the final result $r$ is. Providers can compute $r$ off-chain before they reveal $x_i$, which permits a censorship attack. +- Providers are trusted not to front-run user transactions (via the mempool or colluding with the validator). Providers who observe user transactions can manipulate the result by inserting additional reuests or rotating their commitment. +- Providers are trusted not to keep their hash chain a secret. Anyone with the hash chain can predict the result of a randomness request before it is requested, + and therefore manipulate the result. This applies both to users of the protocol as well as blockchain validators who can use this information to manipulate the on-chain PRNG or reorder user transactions. The code of default deployed provider can be found [here](https://github.com/pyth-network/pyth-crosschain/tree/7bccde484f01c19844b7105d63df207a24018957/apps/fortuna).