Smart Contract

Xorshift128plus

A.b2b7a4c7033eaa24.Xorshift128plus

Valid From

134,199,117

Deployed

6d ago
Feb 22, 2026, 02:18:54 AM UTC

Dependents

0 imports
1import Crypto
2
3/// Defines a xorsift128+ pseudo random generator (PRG) struct used to generate random numbers given some
4/// sourceOfRandomness and salt.
5///
6/// See FLIP 123 for more details: https://github.com/onflow/flips/blob/main/protocol/20230728-commit-reveal.md
7/// And the onflow/random-coin-toss repo for implementation context: https://github.com/onflow/random-coin-toss
8///
9access(all) contract Xorshift128plus {
10
11    /// While not limited to 128 bits of state, this PRG is largely informed by xorshift128+
12    ///
13    access(all) struct PRG {
14
15        // The states below are of type Word64 (instead of UInt64) to prevent overflow/underflow as state evolves
16        //
17        access(all) var state0: Word64
18        access(all) var state1: Word64
19
20        /// Initializer for PRG struct
21        ///
22        /// @param sourceOfRandomness: The entropy bytes used to seed the PRG. It is recommended to use at least 16
23        /// bytes of entropy.
24        /// @param salt: The bytes used to salt the source of randomness
25        ///
26        init(sourceOfRandomness: [UInt8], salt: [UInt8]) {
27            pre {
28                sourceOfRandomness.length >= 16:
29                "Provided entropy lengh=".concat(sourceOfRandomness.length.toString())
30                .concat(" - at least 16 bytes of entropy should be used when initializing the PRG")
31            }
32
33            let tmp: [UInt8] = sourceOfRandomness.concat(salt)
34            // Hash is 32 bytes
35            let hash: [UInt8] = Crypto.hash(tmp, algorithm: HashAlgorithm.SHA3_256)
36            // Reduce the seed to 16 bytes
37            let seed: [UInt8] = hash.slice(from: 0, upTo: 16)
38
39            // Convert the seed bytes to two Word64 values for state initialization
40            let segment0: Word64 = Xorshift128plus._bigEndianBytesToWord64(bytes: seed, start: 0)
41            let segment1: Word64 = Xorshift128plus._bigEndianBytesToWord64(bytes: seed, start: 8)
42
43            // Ensure the initial state is non-zero
44            assert(
45                segment0 != 0 || segment1 != 0,
46                message: "PRG initial state is 0 - must be initialized as non-zero"
47            )
48            
49            self.state0 = segment0
50            self.state1 = segment1
51        }
52
53        /// Advances the PRG state and generates the next UInt64 value
54        /// See https://arxiv.org/pdf/1404.0390.pdf for implementation details and reasoning for triplet selection.
55        /// Note that state only advances when this function is called from a transaction. Calls from within a script
56        /// will not advance state and will return the same value.
57        ///
58        /// @return The next UInt64 value
59        ///
60        access(all) fun nextUInt64(): UInt64 {
61            var a: Word64 = self.state0
62            let b: Word64 = self.state1
63
64            self.state0 = b
65            a = a ^ (a << 23) // a
66            a = a ^ (a >> 17) // b
67            a = a ^ b ^ (b >> 26) // c
68            self.state1 = a
69
70            let randUInt64: UInt64 = UInt64(Word64(a) + Word64(b))
71            return randUInt64
72        }
73
74        /// Advances the PRG state and generates the next UInt256 value by concatenating 4 UInt64 values
75        ///
76        /// @return The next UInt256 value
77        ///
78        access(all)
79        fun nextUInt256(): UInt256 {
80            var res = UInt256(self.nextUInt64())
81            res = res | UInt256(self.nextUInt64()) << 64
82            res = res | UInt256(self.nextUInt64()) << 128
83            res = res | UInt256(self.nextUInt64()) << 192
84
85            return res
86        }
87    }
88
89    /// Helper function to convert an array of big endian bytes to Word64
90    ///
91    /// @param bytes: The bytes to convert
92    /// @param start: The index of the first byte to convert
93    ///
94    /// @return The Word64 value
95    ///
96    access(contract) fun _bigEndianBytesToWord64(bytes: [UInt8], start: Int): Word64 {
97        pre {
98            start + 8 <= bytes.length:
99            "Defined start=".concat(start.toString())
100            .concat(" - at least 8 bytes from the start are required for conversion")
101        }
102        var value: UInt64 = 0
103        var i: Int = 0
104        while i < 8 {
105            value = value << 8 | UInt64(bytes[start + i])
106            i = i + 1
107        }
108        return Word64(value)
109    }
110}
111