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

Testing with 50

Hex Entropy Generator
1 fcb5f92f493e364174410fb8
2 67260badad060c43a22c9fcc
3 28c144bdb3c4330997ff625b
4 f1a448187f473e6ad9c747ef
5 cccf4ff8a27438fdb9640713
Numeric Entropy Generator
1 1835
2 2465
3 2363
4 2432
5 2319
Generated ID: b66ea79f-751c-4b5e-3575-49882
Generated ID: d83e2c71-41ef-4fd3-f426-c9391
Generated ID: 7c1f2bfa-5f00-458f-2519-9f5da
Generated ID: f0cc62c3-a40a-4b3c-9b4a-5d930
Generated ID: 51a46b38-b7f8-413a-2f30-df913
Generated ID: a2525315-68a2-45f6-47b6-80da6
Generated ID: 8e7eabcb-ef71-4eca-d7da-d6c6f
Generated ID: 41ed2362-241c-4c6e-7c03-c1aed
Generated ID: 917a049c-a25d-4d71-d177-a8bbb
Generated ID: 2bbdb9d7-24d0-486e-ee91-d56e8
Generated ID: 76c65ec9-b2b4-4e68-af84-d5673
Generated ID: da9bdecb-78ea-4625-a96c-4037f
Generated ID: d34bddd6-8045-4ee0-6507-6b840
Generated ID: 2414998c-4c2a-4434-2f03-f56c1
Generated ID: 923236b6-a118-4acd-36f3-d8460
Generated ID: 86a09768-295d-47d3-3707-f5709
Generated ID: 312d6ab0-db51-4287-3364-94798
Generated ID: c67bafe8-1b3f-4cec-f623-6d874
Generated ID: 4e2a4c2a-823d-4ba0-3d5f-f950e
Generated ID: 8f0dddb1-3fff-4b9f-78bf-b4e52
Generated ID: e02e9cf9-c2f7-42c2-1472-8f150
Generated ID: 8905a718-aa68-4adc-edd2-2d400
Generated ID: 21d65350-7f33-4173-0a2b-0451d
Generated ID: fc1d7dae-be5e-4423-67ad-9fa17
Generated ID: fa34ffce-e083-43c3-4ab5-6f4fc
Generated ID: 6e9dd911-8bb4-4142-4424-487ff
Generated ID: 7bef1177-9afa-4175-d912-5540b
Generated ID: 5305ef67-d7c8-464e-d36c-c1417
Generated ID: 9ac2ee4e-57df-4551-6634-7f12a
Generated ID: 5278f9b2-fab4-4dd0-acb8-b39b7
Generated ID: e22e9c6c-aab0-4469-a75e-7f33e
Generated ID: cb4e17eb-37c2-48f1-6c9d-9e854
Generated ID: 5e435126-552b-494b-e21d-797e2
Generated ID: 90707eb5-d0ad-45a8-4f73-bd962
Generated ID: e0ab29d2-abec-41f7-ad55-8e6b1
Generated ID: f12db42b-30b5-4b1e-f118-d1022
Generated ID: 60b1af38-c545-4daf-cf0e-e4763
Generated ID: 7124f1c5-eec5-4438-9795-c1a4a
Generated ID: 4c8e3e38-2b55-449c-74f8-5cece
Generated ID: b703dcb7-7ebc-482c-21fb-57c73
Generated ID: 139211e5-d642-4937-9402-16269
Generated ID: 89dd8bbe-23b7-4b77-fe1a-d9059
Generated ID: c66b5b75-d31a-49ee-23bb-10a96
Generated ID: 72f5f3c1-c2ba-4792-dcda-1de18
Generated ID: e05b5e71-5f87-4555-f2e5-7b85e
Generated ID: 487300be-d564-4715-8726-1defc
Generated ID: 2770270f-6dba-412b-8517-21016
Generated ID: 4d1771f0-c3a0-414a-9ede-28756
Generated ID: ce0c4442-7744-49b6-48d6-cebc4
Generated ID: 212fa58e-c8a0-449a-c150-01827
No collisions found

I have tested this with up to 10,000 without collisions. havent tried higher numbers though.

the output from entropyGenerator shows to have a high level entropy across runs, with no discernible repetition seen when producing chunks up to 24chars in length, and when set to numeric mode it produces suitably random outputs in testing.

@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