Last active
March 31, 2024 01:33
-
-
Save Therosin/e8c18f75330cc9106f786f4f02529e40 to your computer and use it in GitHub Desktop.
Lua 5.1 entropy generator, with uuidv4 "like" id generator.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local function positionalHashing(hexString) | |
| local finalNumber = 0 | |
| for i = 1, #hexString do | |
| local char = hexString:sub(i, i) | |
| local numValue = tonumber(char, 16) or 0 | |
| finalNumber = (finalNumber + (numValue * i)) % (2 ^ 53) | |
| end | |
| return finalNumber | |
| end | |
| local function createEntropyGenerator(length, outputAsHex) | |
| local lastOutput = nil -- Store the last output to feed into the next generation | |
| return coroutine.wrap(function() | |
| while true do | |
| local entropyChunk = "" -- Accumulate entropy here | |
| while #entropyChunk < length do | |
| local timeSource = os.clock() + os.time() | |
| local randomFactor = math.random() | |
| -- Incorporate the last output into the entropy source, if available | |
| local combinedEntropy = timeSource * randomFactor * 10000 | |
| if lastOutput then | |
| if type(lastOutput) == 'string' then | |
| combinedEntropy = combinedEntropy + positionalHashing(lastOutput) | |
| elseif type(lastOutput) == 'number' then | |
| combinedEntropy = combinedEntropy + lastOutput | |
| end | |
| end | |
| -- Generate entropy chunk | |
| local entropyHex = string.format("%x", math.floor(combinedEntropy)) | |
| entropyChunk = entropyChunk .. entropyHex -- Concatenate to accumulate | |
| -- Ensure lastOutput updates even within the loop for each chunk added | |
| lastOutput = entropyHex | |
| end | |
| -- Now, entropyChunk is at least 'length' long. Trim if it exceeds. | |
| local outputEntropy = entropyChunk:sub(1, length) | |
| -- Update lastOutput for next generation cycle | |
| if outputAsHex then | |
| lastOutput = outputEntropy | |
| coroutine.yield(outputEntropy) | |
| else | |
| -- Convert the hex string to a number for numeric output | |
| local numericOutput = positionalHashing(outputEntropy) | |
| lastOutput = outputEntropy | |
| coroutine.yield(numericOutput) | |
| end | |
| end | |
| end) | |
| end | |
| local entropyGenerator = createEntropyGenerator(24, true) | |
| --- Generates a random UUIDv4 string | |
| --- NOTE: in the template, 'x' is replaced with a random hex digit (0-9, a-f) and 'y' is replaced with a random hex digit (8-b) this generally conforms to the UUIDv4 spec | |
| --- any other character is left as is. The default template is 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' | |
| ---@param template? string Optional: a template to generate the UUID from. | |
| ---@param debug? boolean Optional: print debug information | |
| ---@return string | |
| local function uuidv4(template, debug) | |
| template = template or 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' | |
| local hexEntropy = entropyGenerator() -- collect entropy from the generator - 24 hex digits at a time. | |
| local counter = 1 -- To iterate through the entropy hex string | |
| local id = string.gsub(template, '[xy]', function(c) | |
| local v = hexEntropy:sub(counter, counter) | |
| counter = counter + 1 | |
| if debug then | |
| print('Replacing', c, 'with', v) | |
| end | |
| return v | |
| end) | |
| return id | |
| end | |
| -- testing | |
| local function test(count) | |
| print("Hex Entropy Generator") | |
| local entropy = createEntropyGenerator(24, true) | |
| for i = 1, 5 do | |
| print(i, entropy()) | |
| end | |
| print("Numeric Entropy Generator") | |
| local numEntropy = createEntropyGenerator(24, false) | |
| for i = 1, 5 do | |
| print(i, numEntropy()) | |
| end | |
| count = count or 1000 | |
| local generated = {} | |
| for i = 1, count do | |
| local id = uuidv4(nil, false) | |
| table.insert(generated, id) | |
| end | |
| local collisions = {} | |
| for i = 1, #generated do | |
| for j = i + 1, #generated do | |
| if generated[i] == generated[j] then | |
| table.insert(collisions, { i, j }) | |
| end | |
| end | |
| end | |
| for i = 1, #generated do | |
| print(string.format('Generated ID: %s', generated[i])) | |
| end | |
| if #collisions == 0 then | |
| print('No collisions found') | |
| else | |
| print(string.format('Found %d collisions', #collisions)) | |
| for i = 1, #collisions do | |
| local collision = collisions[i] | |
| print(string.format('Collision found: [index: %d, id: %s] [index: %d, id: %s]', collision[1], | |
| generated[collision[1]], collision[2], generated[collision[2]])) | |
| end | |
| end | |
| end | |
| -- test(10000) -- Test with 10,000 generated IDs (no collisions expected, default: 1000) | |
| return setmetatable({ | |
| createEntropyGenerator = createEntropyGenerator, | |
| uuidv4 = uuidv4, | |
| test = test | |
| }, { | |
| positionalHashing = positionalHashing | |
| }) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Q: is it efficient, well that depends on usecase, the given example takes 0.04s.
generating chunks of entropy seems fast enough
Running.
Hex Entropy Generator
Output: fcb6108049673ac0744f21c26760e826ad30a071a24f8d2128dac161b7d795b99bbdaa7bf1b0f6647f85c336d9fafe69ccf4b0f7a28a58eeb9651e6e
Numeric Entropy Generator
Output: 20532112181321212380
Exit with code 0.
Take 0.043s.