Created
April 13, 2025 15:30
-
-
Save LolloDev5123/2ac28c3484e97cab3d96037d3e5e0062 to your computer and use it in GitHub Desktop.
Swiss, an Indirecta Crypto Utility Module
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
| -- !native | |
| --[[ | |
| `/shdmmmmmmmmmd-`ymmmddyo:` // sm- /h/ -- | |
| `yNMMMMMMMMMMMMm-.dMMMMMMMMMN+ `MN `-:::.` .-:-hM- -o- .-::. .::-. `.:::` MN--. `-::-. | |
| yMMMMMMMMMMMMMd.:NMMMMMMMMMMMM+ `MN yMs+oNh oNy++mM- +Mo -Mm++:`hmo+yN+ .dmo++- MNoo/ `o+odN: | |
| yMMMMMMMMMMMMy`+NMMMMMMMMMMMMM+ `MN yM: dM. MN yM- +Mo -Mh /Mmss sM+ MN +h ohMo | |
| `yNMMMMMMMMMo`sMMMMMMMMMMMMMNo `MN yM: dM. oNy//dM- +Mo -Mh `dNs++o. -mm+//- dM+/+ mN+/sMo | |
| `/shddddd/ odddddddddddho:` :: .:` -: `:///-` .:. `:- .://:` `-///. `-//: `-///:. | |
| Licensed under GNU General Public License v3.0 | |
| ]] | |
| -- Swiss, an Indirecta Crypto Utility Module | |
| -- LZW Compression + Ascii85 Encoding & Pseudo-Random Number Generation | |
| -- LZW + ASCII85 + PRNG Module in Roblox Luau | |
| local Swiss = {} :: { | |
| -- LZW functions | |
| compress: (input: string) -> string, | |
| decompress: (ascii85Data: string) -> string, | |
| -- ASCII85 functions | |
| ascii85: { | |
| encode: (data: string) -> string, | |
| decode: (ascii85: string) -> string, | |
| }, | |
| -- PRNG functions (53-bit seed based, generating 32-bit numbers) | |
| prng: { | |
| set_seed: (seed: number) -> (), | |
| get_seed: () -> number, | |
| get_random_32: () -> number, | |
| }, | |
| } | |
| ------------------------------------------------------------ | |
| -- LZW Compression/Decompression (using 16-bit code packing) | |
| ------------------------------------------------------------ | |
| function Swiss.compress(input: string): string | |
| -- Initialize dictionary with all single-byte characters. | |
| local dict: { [string]: number } = {} | |
| for i = 0, 255 do | |
| dict[string.char(i)] = i | |
| end | |
| local dictSize: number = 256 | |
| local codes = {} :: { number } | |
| local w = "" | |
| -- Build the dictionary. | |
| for i = 1, #input do | |
| local c = input:sub(i, i) | |
| local wc = w .. c | |
| if dict[wc] then | |
| w = wc | |
| else | |
| table.insert(codes, dict[w]) | |
| dict[wc] = dictSize | |
| dictSize += 1 | |
| w = c | |
| end | |
| end | |
| if w ~= "" then | |
| table.insert(codes, dict[w]) | |
| end | |
| -- Pack 16-bit codes into a binary string. | |
| local bytes = {} :: { string } | |
| for _, code in ipairs(codes) do | |
| -- Write high byte then low byte. | |
| bytes[#bytes + 1] = string.char(math.floor(code / 256)) | |
| bytes[#bytes + 1] = string.char(code % 256) | |
| end | |
| local binaryData = table.concat(bytes) | |
| -- Encode the binary data to Adobe ASCII85 format. | |
| return Swiss.ascii85.encode(binaryData) | |
| end | |
| function Swiss.decompress(ascii85Data: string): string | |
| -- Decode ASCII85 to binary. | |
| local binaryData = Swiss.ascii85.decode(ascii85Data) | |
| local codes = {} :: { number } | |
| -- Unpack 16-bit codes. | |
| for i = 1, #binaryData, 2 do | |
| local high = binaryData:byte(i) | |
| local low = binaryData:byte(i + 1) | |
| table.insert(codes, high * 256 + low) | |
| end | |
| -- Initialize dictionary for decompression. | |
| local dict = {} :: { [number]: string } | |
| for i = 0, 255 do | |
| dict[i] = string.char(i) | |
| end | |
| local dictSize: number = 256 | |
| -- Decompress the codes. | |
| local w = dict[codes[1]] | |
| local resultParts = { w } | |
| for i = 2, #codes do | |
| local k = codes[i] | |
| local entry: string | |
| if dict[k] then | |
| entry = dict[k] | |
| elseif k == dictSize then | |
| entry = w .. w:sub(1, 1) -- special case | |
| else | |
| error("LZW decompress error: Invalid code " .. k) | |
| end | |
| resultParts[#resultParts + 1] = entry | |
| dict[dictSize] = w .. entry:sub(1, 1) | |
| dictSize += 1 | |
| w = entry | |
| end | |
| return table.concat(resultParts) | |
| end | |
| ------------------------------------------------------------ | |
| -- ASCII85 Encode/Decode (Adobe style with <~ and ~> delimiters) | |
| ------------------------------------------------------------ | |
| Swiss.ascii85 = {} :: { | |
| encode: (data: string) -> string, | |
| decode: (ascii85: string) -> string, | |
| } | |
| function Swiss.ascii85.encode(data: string): string | |
| local originalLength = #data | |
| local pad = (4 - (originalLength % 4)) % 4 | |
| local padded = data .. string.rep("\0", pad) | |
| local resultParts = { "<~" } | |
| local groupCount = #padded // 4 | |
| for i = 1, groupCount do | |
| local chunk = padded:sub((i - 1) * 4 + 1, i * 4) | |
| -- Convert 4-byte chunk to a 32-bit unsigned integer. | |
| local value = 0 | |
| for j = 1, 4 do | |
| value = value * 256 + chunk:byte(j) | |
| end | |
| -- For full groups of zero, use shorthand ("z") except in a padded final group. | |
| if value == 0 and not (i == groupCount and pad > 0) then | |
| resultParts[#resultParts + 1] = "z" | |
| else | |
| local block = {} :: { [number]: string } | |
| -- Produce 5 ASCII85 characters. | |
| for j = 1, 5 do | |
| block[j] = string.char((value % 85) + 33) | |
| value = math.floor(value / 85) | |
| end | |
| -- Append characters in reverse order. | |
| for j = 5, 1, -1 do | |
| resultParts[#resultParts + 1] = block[j] | |
| end | |
| end | |
| end | |
| -- Remove extra encoded characters corresponding to padding. | |
| if pad > 0 then | |
| local res = table.concat(resultParts) | |
| res = res:sub(1, #res - (4 - pad)) | |
| resultParts = { res } | |
| end | |
| resultParts[#resultParts + 1] = "~>" | |
| return table.concat(resultParts) | |
| end | |
| function Swiss.ascii85.decode(ascii85: string): string | |
| -- Remove Adobe delimiters. | |
| ascii85 = ascii85:gsub("^%s*<~", ""):gsub("~>%s*$", "") | |
| -- Expand shorthand "z" to "!!!!!". | |
| ascii85 = ascii85:gsub("z", "!!!!!") | |
| local outputBytes = {} :: { string } | |
| local block = {} :: { [number]: number } | |
| local count = 0 | |
| local i = 1 | |
| while i <= #ascii85 do | |
| local c = ascii85:sub(i, i) | |
| if not c:match("%s") then | |
| count += 1 | |
| block[count] = c:byte() - 33 | |
| if count == 5 then | |
| local value = (((block[1] * 85 + block[2]) * 85 + block[3]) * 85 + block[4]) * 85 + block[5] | |
| table.insert(outputBytes, string.char(math.floor(value / 2^24) % 256)) | |
| table.insert(outputBytes, string.char(math.floor(value / 2^16) % 256)) | |
| table.insert(outputBytes, string.char(math.floor(value / 2^8) % 256)) | |
| table.insert(outputBytes, string.char(value % 256)) | |
| count = 0 | |
| end | |
| end | |
| i += 1 | |
| end | |
| if count > 0 then | |
| -- Pad the block if necessary. | |
| for j = count + 1, 5 do | |
| block[j] = 84 -- pad with value corresponding to 'u' | |
| end | |
| local value = (((block[1] * 85 + block[2]) * 85 + block[3]) * 85 + block[4]) * 85 + block[5] | |
| -- Output only count - 1 bytes. | |
| for j = 1, count - 1 do | |
| table.insert(outputBytes, string.char(math.floor(value / (2^(8 * (4 - j))) ) % 256)) | |
| end | |
| end | |
| return table.concat(outputBytes) | |
| end | |
| ------------------------------------------------------------ | |
| -- PRNG Implementation (53-bit seed based, generating 32-bit integers) | |
| ------------------------------------------------------------ | |
| Swiss.prng = {} :: { | |
| set_seed: (seed: number) -> (), | |
| get_seed: () -> number, | |
| get_random_32: () -> number, | |
| } | |
| -- Internal PRNG state (53 bits in total) | |
| local prng_state_45 = 0 -- Range: 0 .. (2^45 - 1) | |
| local prng_state_8 = 2 -- Range: 2 .. 256 | |
| -- Secret keys (derived from 57 secret bits) | |
| local secret_key_6 = 58 -- 6-bit arbitrary (0..63) | |
| local secret_key_7 = 110 -- 7-bit arbitrary (0..127) | |
| local secret_key_44 = 3580861008710 -- 44-bit arbitrary (0..17592186044415) | |
| local floor = math.floor | |
| -- Compute a primitive root modulo 257 (one of 128 possible roots) | |
| local function primitive_root_257(idx: number): number | |
| local g, m, d = 1, 128, 2 * idx + 1 | |
| repeat | |
| -- Multiply by itself and adjust with multiplier (3 when condition met) | |
| g = (g * g * ((d >= m) and 3 or 1)) % 257 | |
| m = m / 2 | |
| d = d % m | |
| until m < 1 | |
| return g | |
| end | |
| local param_mul_8 = primitive_root_257(secret_key_7) | |
| local param_mul_45 = secret_key_6 * 4 + 1 | |
| local param_add_45 = secret_key_44 * 2 + 1 | |
| local PRNG_MOD_45 = 35184372088832 -- 2^45 | |
| function Swiss.prng.set_seed(seed_53: number) | |
| -- Set the internal state from a 53-bit seed. | |
| prng_state_45 = seed_53 % PRNG_MOD_45 | |
| prng_state_8 = floor(seed_53 / PRNG_MOD_45) % 255 + 2 | |
| end | |
| function Swiss.prng.get_seed(): number | |
| -- Return the current 53-bit seed representing the internal state. | |
| return (prng_state_8 - 2) * PRNG_MOD_45 + prng_state_45 | |
| end | |
| function Swiss.prng.get_random_32(): number | |
| -- Update state_45 using an LCG with full period 2^45. | |
| prng_state_45 = (prng_state_45 * param_mul_45 + param_add_45) % PRNG_MOD_45 | |
| -- Update state_8 using a Lehmer RNG; skip state = 1 to ensure coprime with 2^45. | |
| repeat | |
| prng_state_8 = (prng_state_8 * param_mul_8) % 257 | |
| until prng_state_8 ~= 1 | |
| -- Mix the two state parts: shift and rotate "state_45" by a variable amount. | |
| local r = prng_state_8 % 32 | |
| local n = floor(prng_state_45 / 2^(13 - (prng_state_8 - r) / 32)) % 2^32 / 2^r | |
| return floor((n % 1) * 2^32) + floor(n) | |
| end | |
| ------------------------------------------------------------ | |
| -- Module Return | |
| ------------------------------------------------------------ | |
| return Swiss |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment