Skip to content

Instantly share code, notes, and snippets.

@Therosin
Last active March 31, 2024 01:33
Show Gist options
  • Select an option

  • Save Therosin/e8c18f75330cc9106f786f4f02529e40 to your computer and use it in GitHub Desktop.

Select an option

Save Therosin/e8c18f75330cc9106f786f4f02529e40 to your computer and use it in GitHub Desktop.
Lua 5.1 entropy generator, with uuidv4 "like" id generator.
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
})
@Therosin
Copy link
Author

Q: is it efficient, well that depends on usecase, the given example takes 0.04s.

generating chunks of entropy seems fast enough

print("Hex Entropy Generator")
local stringEntropy = ""
local entropy = createEntropyGenerator(24, true)
for i = 1, 5 do
    stringEntropy = stringEntropy .. entropy()
end
print("Output: " .. stringEntropy .. "\n")

print("Numeric Entropy Generator")
local numberEntropy = ""
local numEntropy = createEntropyGenerator(24, false)
for i = 1, 5 do
    numberEntropy = numberEntropy .. tostring(numEntropy())
end
print("Output: " .. numberEntropy .. "\n")

Running.
Hex Entropy Generator
Output: fcb6108049673ac0744f21c26760e826ad30a071a24f8d2128dac161b7d795b99bbdaa7bf1b0f6647f85c336d9fafe69ccf4b0f7a28a58eeb9651e6e

Numeric Entropy Generator
Output: 20532112181321212380

Exit with code 0.
Take 0.043s.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment