diff --git a/README.md b/README.md index 963ca46..d816f64 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ All functions that take secret input may query the library's random generator, hoping for the best like other libraries do, CCryptoLib shifts that burden into *you!* +### Initializing using a Trusted Web Source If you trust the tmpim Krist node, you can fetch a socket token and use it for initialization: ```lua @@ -34,5 +35,10 @@ local random = require "ccryptolib.random" random.init(os.time("nano")) ``` -Otherwise, you will need to find another high-quality random entropy source to -initialize the generator. **DO NOT INITIALIZE USING MATH.RANDOM.** +### Initializing using VM Instruction Counting +As of v1.2.0, you can also initialize the generator using VM instruction timing noise. +See the `random.initWithTiming` method for security risks of taking this approach. +```lua +local random = require "ccryptolib.random" +random.initWithTiming() +``` diff --git a/ccryptolib/random.lua b/ccryptolib/random.lua index 60c0389..459013b 100644 --- a/ccryptolib/random.lua +++ b/ccryptolib/random.lua @@ -27,6 +27,55 @@ local function init(seed) initialized = true end +--- Returns whether the generator has been initialized or not. +--- @return boolean +local function isInit() + return initialized +end + +--- Initializes the generator using VM instruction timing noise. +--- +--- This function counts how many instructions the VM can execute within a single +--- millisecond, and mixes the lower bits of these values into the generator state. +--- The current implementation collects data for 512 ms and takes the lower 8 bits from +--- each count. +--- +--- Compared to fetching entropy from a trusted web source, this approach is riskier but +--- more convenient. The factors that influence instruction timing suggest that this +--- seed is unpredictable for other players, but this assumption might turn out to be +--- untrue. +local function initWithTiming() + assert(os.time() ~= 0) + + local f = assert(load("local e=os.time return{" .. ("e(),"):rep(256) .. "}")) + + do -- Warmup. + local t = f() + while t[256] - t[1] > 1 do t = f() end + end + + -- Fill up the buffer. + local buf = {} + for i = 1, 512 do + local t = f() + while t[256] == t[1] do t = f() end + for j = 1, 256 do + if t[j] ~= t[1] then + buf[i] = j - 1 + break + end + end + end + + -- Perform a histogram check to catch faulty os.epoch implementations. + local hist = {} + for i = 0, 255 do hist[i] = 0 end + for i = 1, #buf do hist[buf[i]] = hist[buf[i]] + 1 end + for i = 0, 255 do assert(hist[i] < 20) end + + init(string.char(table.unpack(buf))) +end + --- Mixes extra entropy into the generator state. --- @param data string The additional entropy to mix. local function mix(data) @@ -49,6 +98,8 @@ end return { init = init, + isInit = isInit, + initWithTiming = initWithTiming, mix = mix, random = random, }