From b1b0b1aad65b57900e18539d6f25b38e51801822 Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Sat, 2 May 2026 23:34:21 +0800 Subject: feat: implement hardware random number generation and early PRNG initialization --- na/arch/riscv/include/arch/random.h | 27 +++++++++ na/arch/riscv/lib/random.cc | 21 +++++++ na/include/lume/random.h | 26 ++++++++ na/lib/random.cc | 116 ++++++++++++++++++++++++++++++++++++ 4 files changed, 190 insertions(+) create mode 100644 na/arch/riscv/include/arch/random.h create mode 100644 na/arch/riscv/lib/random.cc create mode 100644 na/include/lume/random.h create mode 100644 na/lib/random.cc diff --git a/na/arch/riscv/include/arch/random.h b/na/arch/riscv/include/arch/random.h new file mode 100644 index 0000000..8c8c051 --- /dev/null +++ b/na/arch/riscv/include/arch/random.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#include + +namespace arch { + +/* + * Get a random 64-bit value from the hardware TRNG. + * Returns true if supported and successful, false otherwise. + */ +bool get_hw_random(uint64& value); + +/* + * Get the current high-resolution hardware cycle counter. + * Used as an entropy fallback (Jitter/Timer). + */ +inline uint64 get_cycles() +{ + uint64 cycles; + __asm__ volatile("rdcycle %0" : "=r"(cycles)); + return cycles; +} + +} // namespace arch diff --git a/na/arch/riscv/lib/random.cc b/na/arch/riscv/lib/random.cc new file mode 100644 index 0000000..afc0078 --- /dev/null +++ b/na/arch/riscv/lib/random.cc @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#include +#include + +namespace arch { + +bool get_hw_random(uint64& value) +{ + /* + * Real implementation would check for Zkr extension and execute: + * csrr x, seed + * + * Since QEMU virt doesn't guarantee Zkr, we return false to + * force the MI layer to use the timer fallback. + */ + return false; +} + +} // namespace arch diff --git a/na/include/lume/random.h b/na/include/lume/random.h new file mode 100644 index 0000000..0705b1b --- /dev/null +++ b/na/include/lume/random.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#include + +/* + * MI: Machine-Independent Random Number Generator API + * + * Provides early PRNG and eventually a full CSPRNG. + */ + +/* + * Initialize early PRNG with a seed (e.g., from FDT). + * If seed is 0, attempts to use hardware TRNG or falls back to timer jitter. + */ +void random_early_init(uint64 seed); + +/* Consume random bits */ +uint64 get_random_u64(); +uint32 get_random_u32(); +void get_random_bytes(void* buf, size_t len); + +/* Inject entropy (for future interrupt/device mixing) */ +void add_device_randomness(const void* buf, size_t len); diff --git a/na/lib/random.cc b/na/lib/random.cc new file mode 100644 index 0000000..c607e5f --- /dev/null +++ b/na/lib/random.cc @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#include +#include +#include + +#include + +/* + * Early Boot PRNG (xoshiro256**) + * State size: 256 bits (4 * 64-bit words) + * Extremely fast, excellent statistical properties. + */ + +static uint64 g_state[4]; +static bool g_initialized = false; + +static inline uint64 rotl(const uint64 x, int k) { + return (x << k) | (x >> (64 - k)); +} + +/* SplitMix64 used to initialize xoshiro state from a single 64-bit seed */ +static uint64 splitmix64(uint64& state) { + uint64 z = (state += 0x9e3779b97f4a7c15ULL); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL; + z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL; + return z ^ (z >> 31); +} + +void random_early_init(uint64 seed) +{ + if (g_initialized) return; + + /* If seed is 0 (not provided by bootloader), try hardware */ + if (seed == 0) { + uint64 hw_val; + if (arch::get_hw_random(hw_val)) { + seed = hw_val; + } else { + /* Fallback: use boot cycles mixed with a constant */ + seed = arch::get_cycles() ^ 0xdeadbeefbadc0ffeULL; + } + } + + uint64 sm_state = seed; + g_state[0] = splitmix64(sm_state); + g_state[1] = splitmix64(sm_state); + g_state[2] = splitmix64(sm_state); + g_state[3] = splitmix64(sm_state); + + g_initialized = true; + kprintf("[random] early PRNG initialized, seed=0x%llx\n", seed); +} + +uint64 get_random_u64() +{ + if (!g_initialized) { + random_early_init(0); + } + + const uint64 result = rotl(g_state[1] * 5, 7) * 9; + + const uint64 t = g_state[1] << 17; + + g_state[2] ^= g_state[0]; + g_state[3] ^= g_state[1]; + g_state[1] ^= g_state[2]; + g_state[0] ^= g_state[3]; + + g_state[2] ^= t; + g_state[3] = rotl(g_state[3], 45); + + /* Mix with current cycles for slight additional unpredictability */ + return result ^ arch::get_cycles(); +} + +uint32 get_random_u32() +{ + return static_cast(get_random_u64() >> 32); +} + +void get_random_bytes(void* buf, size_t len) +{ + uint8* p = static_cast(buf); + while (len >= 8) { + uint64 val = get_random_u64(); + for (int i = 0; i < 8; i++) { + p[i] = (val >> (i * 8)) & 0xFF; + } + p += 8; + len -= 8; + } + if (len > 0) { + uint64 val = get_random_u64(); + for (size_t i = 0; i < len; i++) { + p[i] = (val >> (i * 8)) & 0xFF; + } + } +} + +void add_device_randomness(const void* buf, size_t len) +{ + /* + * For now, just mix into the xoshiro state using a simple XOR. + * In the future, this will feed the CSPRNG entropy pool. + */ + const uint8* p = static_cast(buf); + uint64 mix = arch::get_cycles(); + for (size_t i = 0; i < len; i++) { + mix = (mix << 5) ^ (mix >> 59) ^ p[i]; + } + + g_state[0] ^= mix; + g_state[1] ^= arch::get_cycles(); +} -- cgit v1.2.3