Kaynağa Gözat

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
Bailey Nottingham 1 yıl önce
ebeveyn
işleme
e6ae23b7de

+ 110 - 0
target_chains/ethereum/contracts/forge-test/PRNG.t.sol

@@ -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 - 0
target_chains/ethereum/entropy_sdk/solidity/PRNG.sol

@@ -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 - 0
target_chains/ethereum/entropy_sdk/solidity/README.md

@@ -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 - 0
target_chains/ethereum/entropy_sdk/solidity/abis/PRNG.json

@@ -0,0 +1,13 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "_seed",
+        "type": "bytes32"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  }
+]

+ 1 - 1
target_chains/ethereum/entropy_sdk/solidity/package.json

@@ -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": [