1 // Copyright 2020 The Pigweed Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 // use this file except in compliance with the License. You may obtain a copy of 5 // the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations under 13 // the License. 14 #pragma once 15 16 #include <cstdint> 17 #include <cstring> 18 #include <span> 19 20 #include "pw_bytes/span.h" 21 #include "pw_random/random.h" 22 #include "pw_status/status_with_size.h" 23 24 namespace pw::random { 25 26 // This is the "xorshift*" algorithm which is a bit stronger than plain XOR 27 // shift thanks to the nonlinear transformation at the end (multiplication). 28 // 29 // See: https://en.wikipedia.org/wiki/Xorshift 30 // 31 // This random generator is NOT cryptographically secure, and incorporates 32 // pseudo-random generation to extrapolate any true injected entropy. The 33 // distribution is not guaranteed to be uniform. 34 class XorShiftStarRng64 : public RandomGenerator { 35 public: XorShiftStarRng64(uint64_t initial_seed)36 XorShiftStarRng64(uint64_t initial_seed) : state_(initial_seed) {} 37 38 // This generator uses entropy-seeded PRNG to never exhaust its random number 39 // pool. Get(ByteSpan dest)40 StatusWithSize Get(ByteSpan dest) final { 41 const size_t bytes_written = dest.size_bytes(); 42 while (!dest.empty()) { 43 uint64_t random = Regenerate(); 44 size_t copy_size = std::min(dest.size_bytes(), sizeof(state_)); 45 std::memcpy(dest.data(), &random, copy_size); 46 dest = dest.subspan(copy_size); 47 } 48 49 return StatusWithSize(bytes_written); 50 } 51 52 // Entropy is injected by rotating the state by the number of entropy bits 53 // before xoring the entropy with the current state. This ensures seeding 54 // the random value with single bits will progressively fill the state with 55 // more entropy. InjectEntropyBits(uint32_t data,uint_fast8_t num_bits)56 void InjectEntropyBits(uint32_t data, uint_fast8_t num_bits) final { 57 if (num_bits == 0) { 58 return; 59 } else if (num_bits > 32) { 60 num_bits = 32; 61 } 62 63 // Rotate state. 64 uint64_t untouched_state = state_ >> (kNumStateBits - num_bits); 65 state_ = untouched_state | (state_ << num_bits); 66 // Zero-out all irrelevant bits, then XOR entropy into state. 67 uint32_t mask = (1 << num_bits) - 1; 68 state_ ^= (data & mask); 69 } 70 71 private: 72 // Calculate and return the next value based on the "xorshift*" algorithm Regenerate()73 uint64_t Regenerate() { 74 // State must be nonzero, or the algorithm will get stuck and always return 75 // zero. 76 if (state_ == 0) { 77 state_--; 78 } 79 state_ ^= state_ >> 12; 80 state_ ^= state_ << 25; 81 state_ ^= state_ >> 27; 82 return state_ * kMultConst; 83 } 84 uint64_t state_; 85 static constexpr uint8_t kNumStateBits = sizeof(state_) * 8; 86 87 // For information on why this constant was selected, see: 88 // https://www.jstatsoft.org/article/view/v008i14 89 // http://vigna.di.unimi.it/ftp/papers/xorshift.pdf 90 static constexpr uint64_t kMultConst = 0x2545F4914F6CDD1D; 91 }; 92 93 } // namespace pw::random 94