Skip to content

Commit

Permalink
PRNG Library with Pyth Entropy Integration (#1733)
Browse files Browse the repository at this point in the history
* Pyth Entropy PRNG library

* Update to a contract to store internal state

* Add seed setter function

* Make functions internal

* Add tests and address PR comments

* Run end-of-file fixer
  • Loading branch information
baileynottingham authored Jul 24, 2024
1 parent 5a2e4a8 commit e6ae23b
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 1 deletion.
110 changes: 110 additions & 0 deletions target_chains/ethereum/contracts/forge-test/PRNG.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;

import "@pythnetwork/entropy-sdk-solidity/PRNG.sol";
import "forge-std/Test.sol";

contract PRNGTestHelper is PRNG {
constructor(bytes32 _seed) PRNG(_seed) {}

function publicNextBytes32() public returns (bytes32) {
return nextBytes32();
}

function publicRandUint() public returns (uint256) {
return randUint();
}

function publicRandUint64() public returns (uint64) {
return randUint64();
}

function publicRandUintRange(
uint256 min,
uint256 max
) public returns (uint256) {
return randUintRange(min, max);
}

function publicRandomPermutation(
uint256 length
) public returns (uint256[] memory) {
return randomPermutation(length);
}
}

contract PRNGTest is Test {
PRNGTestHelper prng;

function setUp() public {
prng = new PRNGTestHelper(keccak256(abi.encode("initial seed")));
}

function testNextBytes32() public {
bytes32 randomValue1 = prng.publicNextBytes32();
bytes32 randomValue2 = prng.publicNextBytes32();

assertNotEq(
randomValue1,
randomValue2,
"Random values should not be equal"
);
}

function testRandUint() public {
uint256 randomValue1 = prng.publicRandUint();
uint256 randomValue2 = prng.publicRandUint();

assertNotEq(
randomValue1,
randomValue2,
"Random values should not be equal"
);
}

function testRandUint64() public {
uint64 randomValue1 = prng.publicRandUint64();
uint64 randomValue2 = prng.publicRandUint64();

assertNotEq(
randomValue1,
randomValue2,
"Random values should not be equal"
);
}

function testRandUintRange() public {
uint256 min = 10;
uint256 max = 20;

for (uint256 i = 0; i < 100; i++) {
uint256 randomValue = prng.publicRandUintRange(min, max);
assertGe(
randomValue,
min,
"Random value should be greater than or equal to min"
);
assertLt(randomValue, max, "Random value should be less than max");
}
}

function testRandomPermutation() public {
uint256 length = 5;
uint256[] memory permutation = prng.publicRandomPermutation(length);

assertEq(permutation.length, length, "Permutation length should match");

bool[] memory found = new bool[](length);
for (uint256 i = 0; i < length; i++) {
assertLt(
permutation[i],
length,
"Permutation value should be within range"
);
found[permutation[i]] = true;
}
for (uint256 i = 0; i < length; i++) {
assertTrue(found[i], "Permutation should contain all values");
}
}
}
77 changes: 77 additions & 0 deletions target_chains/ethereum/entropy_sdk/solidity/PRNG.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;

/// @title PRNG Contract
/// @notice A contract for pseudorandom number generation and related utility functions
/// @dev This PRNG contract is designed to work with Pyth Entropy as the seed source.
/// Pyth Entropy provides secure, rapid random number generation for blockchain applications,
/// enabling responsive UX for NFT mints, games, and other use cases requiring randomness.
contract PRNG {
bytes32 private seed;
uint256 private nonce;

/// @notice Initialize the PRNG with a seed
/// @param _seed The Pyth Entropy seed (bytes32)
constructor(bytes32 _seed) {
seed = _seed;
nonce = 0;
}

/// @notice Set a new seed and reset the nonce
/// @param _newSeed The new seed (bytes32)
function setSeed(bytes32 _newSeed) internal {
seed = _newSeed;
nonce = 0;
}

/// @notice Generate the next random bytes32 value and update the state
/// @return The next random bytes32 value
function nextBytes32() internal returns (bytes32) {
bytes32 result = keccak256(abi.encode(seed, nonce));
nonce++;
return result;
}

/// @notice Generate a random uint256 value
/// @return A random uint256 value
function randUint() internal returns (uint256) {
return uint256(nextBytes32());
}

/// @notice Generate a random uint64 value
/// @return A random uint64 value
function randUint64() internal returns (uint64) {
return uint64(uint256(nextBytes32()));
}

/// @notice Generate a random uint256 value within a specified range
/// @param min The minimum value (inclusive)
/// @param max The maximum value (exclusive)
/// @return A random uint256 value between min and max
/// @dev The result is uniformly distributed between min and max, with a slight bias toward lower numbers.
/// @dev This bias is insignificant as long as (max - min) << MAX_UINT256.
function randUintRange(
uint256 min,
uint256 max
) internal returns (uint256) {
require(max > min, "Max must be greater than min");
return (randUint() % (max - min)) + min;
}

/// @notice Generate a random permutation of a sequence
/// @param length The length of the sequence to permute
/// @return A randomly permuted array of uint256 values
function randomPermutation(
uint256 length
) internal returns (uint256[] memory) {
uint256[] memory permutation = new uint256[](length);
for (uint256 i = 0; i < length; i++) {
permutation[i] = i;
}
for (uint256 i = 0; i < length; i++) {
uint256 j = i + (randUint() % (length - i));
(permutation[i], permutation[j]) = (permutation[j], permutation[i]);
}
return permutation;
}
}
44 changes: 44 additions & 0 deletions target_chains/ethereum/entropy_sdk/solidity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,47 @@ This method will combine the user and provider's random numbers, along with the

The [Coin Flip](/target_chains/ethereum/examples/coin_flip) example demonstrates how to build a smart contract that
interacts with Pyth Entropy as well as a typescript client for that application.

## PRNG Contract

The PRNG (Pseudorandom Number Generation) Contract is designed to work seamlessly with Pyth Entropy.

### Features

- **Pyth Entropy Integration**: Utilizes Pyth Entropy as a secure seed source
- **Stateful Randomness**: Maintains an internal state to ensure unique random numbers on each call
- **Versatile Random Generation**: Includes functions for generating random uint256, uint64, integers within specified ranges, and permutations
- **Random Bytes Generation**: Ability to generate random byte sequences of specified length

### Key Functions

- `randUint() -> uint256`: Generate a random uint256 value
- `randUint64() -> uint64`: Generate a random uint64 value
- `randUintRange(uint256 min, uint256 max) -> uint256`: Generate a random integer within a specified range
- `randomBytes(uint256 length) -> bytes`: Generate a sequence of random bytes
- `randomPermutation(uint256 length) -> uint256[]`: Generate a random permutation of a sequence

### Usage

To use the PRNG contract in your project:

1. Create a contract that inherits from PRNG and uses its internal functions with a seed from Pyth Entropy:

```solidity
contract MyContract is PRNG {
constructor(bytes32 _seed) {
PRNG(_seed);
}
}
```

2. Use the contract functions to generate random numbers:

```solidity
bytes32 pythEntropySeed = ...; // Get this from Pyth Entropy
setSeed(pythEntropySeed)
uint256 randomNumber = randUint();
uint64 randomSmallNumber = randUint64();
uint256 randomInRange = randUintRange(1, 100);
```
13 changes: 13 additions & 0 deletions target_chains/ethereum/entropy_sdk/solidity/abis/PRNG.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
{
"inputs": [
{
"internalType": "bytes32",
"name": "_seed",
"type": "bytes32"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
}
]
2 changes: 1 addition & 1 deletion target_chains/ethereum/entropy_sdk/solidity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"scripts": {
"format": "prettier --write .",
"generate-abi": "generate-abis IEntropy IEntropyConsumer EntropyErrors EntropyEvents EntropyStructs",
"generate-abi": "generate-abis IEntropy IEntropyConsumer EntropyErrors EntropyEvents EntropyStructs PRNG",
"check-abi": "git diff --exit-code abis"
},
"keywords": [
Expand Down

0 comments on commit e6ae23b

Please sign in to comment.