Skip to content

Commit e6ae23b

Browse files
PRNG Library with Pyth Entropy Integration (#1733)
* 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
1 parent 5a2e4a8 commit e6ae23b

File tree

5 files changed

+245
-1
lines changed

5 files changed

+245
-1
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// SPDX-License-Identifier: Apache 2
2+
pragma solidity ^0.8.0;
3+
4+
import "@pythnetwork/entropy-sdk-solidity/PRNG.sol";
5+
import "forge-std/Test.sol";
6+
7+
contract PRNGTestHelper is PRNG {
8+
constructor(bytes32 _seed) PRNG(_seed) {}
9+
10+
function publicNextBytes32() public returns (bytes32) {
11+
return nextBytes32();
12+
}
13+
14+
function publicRandUint() public returns (uint256) {
15+
return randUint();
16+
}
17+
18+
function publicRandUint64() public returns (uint64) {
19+
return randUint64();
20+
}
21+
22+
function publicRandUintRange(
23+
uint256 min,
24+
uint256 max
25+
) public returns (uint256) {
26+
return randUintRange(min, max);
27+
}
28+
29+
function publicRandomPermutation(
30+
uint256 length
31+
) public returns (uint256[] memory) {
32+
return randomPermutation(length);
33+
}
34+
}
35+
36+
contract PRNGTest is Test {
37+
PRNGTestHelper prng;
38+
39+
function setUp() public {
40+
prng = new PRNGTestHelper(keccak256(abi.encode("initial seed")));
41+
}
42+
43+
function testNextBytes32() public {
44+
bytes32 randomValue1 = prng.publicNextBytes32();
45+
bytes32 randomValue2 = prng.publicNextBytes32();
46+
47+
assertNotEq(
48+
randomValue1,
49+
randomValue2,
50+
"Random values should not be equal"
51+
);
52+
}
53+
54+
function testRandUint() public {
55+
uint256 randomValue1 = prng.publicRandUint();
56+
uint256 randomValue2 = prng.publicRandUint();
57+
58+
assertNotEq(
59+
randomValue1,
60+
randomValue2,
61+
"Random values should not be equal"
62+
);
63+
}
64+
65+
function testRandUint64() public {
66+
uint64 randomValue1 = prng.publicRandUint64();
67+
uint64 randomValue2 = prng.publicRandUint64();
68+
69+
assertNotEq(
70+
randomValue1,
71+
randomValue2,
72+
"Random values should not be equal"
73+
);
74+
}
75+
76+
function testRandUintRange() public {
77+
uint256 min = 10;
78+
uint256 max = 20;
79+
80+
for (uint256 i = 0; i < 100; i++) {
81+
uint256 randomValue = prng.publicRandUintRange(min, max);
82+
assertGe(
83+
randomValue,
84+
min,
85+
"Random value should be greater than or equal to min"
86+
);
87+
assertLt(randomValue, max, "Random value should be less than max");
88+
}
89+
}
90+
91+
function testRandomPermutation() public {
92+
uint256 length = 5;
93+
uint256[] memory permutation = prng.publicRandomPermutation(length);
94+
95+
assertEq(permutation.length, length, "Permutation length should match");
96+
97+
bool[] memory found = new bool[](length);
98+
for (uint256 i = 0; i < length; i++) {
99+
assertLt(
100+
permutation[i],
101+
length,
102+
"Permutation value should be within range"
103+
);
104+
found[permutation[i]] = true;
105+
}
106+
for (uint256 i = 0; i < length; i++) {
107+
assertTrue(found[i], "Permutation should contain all values");
108+
}
109+
}
110+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// SPDX-License-Identifier: Apache 2
2+
pragma solidity ^0.8.0;
3+
4+
/// @title PRNG Contract
5+
/// @notice A contract for pseudorandom number generation and related utility functions
6+
/// @dev This PRNG contract is designed to work with Pyth Entropy as the seed source.
7+
/// Pyth Entropy provides secure, rapid random number generation for blockchain applications,
8+
/// enabling responsive UX for NFT mints, games, and other use cases requiring randomness.
9+
contract PRNG {
10+
bytes32 private seed;
11+
uint256 private nonce;
12+
13+
/// @notice Initialize the PRNG with a seed
14+
/// @param _seed The Pyth Entropy seed (bytes32)
15+
constructor(bytes32 _seed) {
16+
seed = _seed;
17+
nonce = 0;
18+
}
19+
20+
/// @notice Set a new seed and reset the nonce
21+
/// @param _newSeed The new seed (bytes32)
22+
function setSeed(bytes32 _newSeed) internal {
23+
seed = _newSeed;
24+
nonce = 0;
25+
}
26+
27+
/// @notice Generate the next random bytes32 value and update the state
28+
/// @return The next random bytes32 value
29+
function nextBytes32() internal returns (bytes32) {
30+
bytes32 result = keccak256(abi.encode(seed, nonce));
31+
nonce++;
32+
return result;
33+
}
34+
35+
/// @notice Generate a random uint256 value
36+
/// @return A random uint256 value
37+
function randUint() internal returns (uint256) {
38+
return uint256(nextBytes32());
39+
}
40+
41+
/// @notice Generate a random uint64 value
42+
/// @return A random uint64 value
43+
function randUint64() internal returns (uint64) {
44+
return uint64(uint256(nextBytes32()));
45+
}
46+
47+
/// @notice Generate a random uint256 value within a specified range
48+
/// @param min The minimum value (inclusive)
49+
/// @param max The maximum value (exclusive)
50+
/// @return A random uint256 value between min and max
51+
/// @dev The result is uniformly distributed between min and max, with a slight bias toward lower numbers.
52+
/// @dev This bias is insignificant as long as (max - min) << MAX_UINT256.
53+
function randUintRange(
54+
uint256 min,
55+
uint256 max
56+
) internal returns (uint256) {
57+
require(max > min, "Max must be greater than min");
58+
return (randUint() % (max - min)) + min;
59+
}
60+
61+
/// @notice Generate a random permutation of a sequence
62+
/// @param length The length of the sequence to permute
63+
/// @return A randomly permuted array of uint256 values
64+
function randomPermutation(
65+
uint256 length
66+
) internal returns (uint256[] memory) {
67+
uint256[] memory permutation = new uint256[](length);
68+
for (uint256 i = 0; i < length; i++) {
69+
permutation[i] = i;
70+
}
71+
for (uint256 i = 0; i < length; i++) {
72+
uint256 j = i + (randUint() % (length - i));
73+
(permutation[i], permutation[j]) = (permutation[j], permutation[i]);
74+
}
75+
return permutation;
76+
}
77+
}

target_chains/ethereum/entropy_sdk/solidity/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,47 @@ This method will combine the user and provider's random numbers, along with the
101101

102102
The [Coin Flip](/target_chains/ethereum/examples/coin_flip) example demonstrates how to build a smart contract that
103103
interacts with Pyth Entropy as well as a typescript client for that application.
104+
105+
## PRNG Contract
106+
107+
The PRNG (Pseudorandom Number Generation) Contract is designed to work seamlessly with Pyth Entropy.
108+
109+
### Features
110+
111+
- **Pyth Entropy Integration**: Utilizes Pyth Entropy as a secure seed source
112+
- **Stateful Randomness**: Maintains an internal state to ensure unique random numbers on each call
113+
- **Versatile Random Generation**: Includes functions for generating random uint256, uint64, integers within specified ranges, and permutations
114+
- **Random Bytes Generation**: Ability to generate random byte sequences of specified length
115+
116+
### Key Functions
117+
118+
- `randUint() -> uint256`: Generate a random uint256 value
119+
- `randUint64() -> uint64`: Generate a random uint64 value
120+
- `randUintRange(uint256 min, uint256 max) -> uint256`: Generate a random integer within a specified range
121+
- `randomBytes(uint256 length) -> bytes`: Generate a sequence of random bytes
122+
- `randomPermutation(uint256 length) -> uint256[]`: Generate a random permutation of a sequence
123+
124+
### Usage
125+
126+
To use the PRNG contract in your project:
127+
128+
1. Create a contract that inherits from PRNG and uses its internal functions with a seed from Pyth Entropy:
129+
130+
```solidity
131+
contract MyContract is PRNG {
132+
constructor(bytes32 _seed) {
133+
PRNG(_seed);
134+
}
135+
}
136+
137+
```
138+
139+
2. Use the contract functions to generate random numbers:
140+
141+
```solidity
142+
bytes32 pythEntropySeed = ...; // Get this from Pyth Entropy
143+
setSeed(pythEntropySeed)
144+
uint256 randomNumber = randUint();
145+
uint64 randomSmallNumber = randUint64();
146+
uint256 randomInRange = randUintRange(1, 100);
147+
```
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[
2+
{
3+
"inputs": [
4+
{
5+
"internalType": "bytes32",
6+
"name": "_seed",
7+
"type": "bytes32"
8+
}
9+
],
10+
"stateMutability": "nonpayable",
11+
"type": "constructor"
12+
}
13+
]

target_chains/ethereum/entropy_sdk/solidity/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
"scripts": {
1414
"format": "prettier --write .",
15-
"generate-abi": "generate-abis IEntropy IEntropyConsumer EntropyErrors EntropyEvents EntropyStructs",
15+
"generate-abi": "generate-abis IEntropy IEntropyConsumer EntropyErrors EntropyEvents EntropyStructs PRNG",
1616
"check-abi": "git diff --exit-code abis"
1717
},
1818
"keywords": [

0 commit comments

Comments
 (0)