From dca9502b0e03dde49ada0c1ca85dba9c048d1b5c Mon Sep 17 00:00:00 2001 From: bobinstein Date: Tue, 29 Oct 2024 18:13:42 -0400 Subject: [PATCH 1/9] feat: Vault draft --- processes/vault.lua | 163 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 processes/vault.lua diff --git a/processes/vault.lua b/processes/vault.lua new file mode 100644 index 0000000..f92ee24 --- /dev/null +++ b/processes/vault.lua @@ -0,0 +1,163 @@ +local json = require('json') + +SecretVault = SecretVault or {} +SecretVault.State = SecretVault.State or {} +SecretVault.PubSub = SecretVault.PubSub or { ao.env.process.Owner } +SecretVault.Controllers = SecretVault.Controllers or { ao.env.process.Owner } + +SecretVault.actions = { + set = 'Set', + get = 'Get', + addController = "Add-Controller", + addPubSub = "Add-PubSub" +} + +function SecretVault.authorized(From) + for _, value in ipairs(SecretVault.Controllers) do + if value == From then + return true + end + end + return false +end + +-- Set value in nested table based on dot-separated path +local function setNestedValue(table, path, value) + local current = table + for segment in string.gmatch(path, "[^%.]+") do + if not current[segment] then + current[segment] = {} + end + current = current[segment] + end + current = value +end + +-- Notify all PubSub users of a change +local function notifyPubSub(action, path, newController) + for _, subscriber in ipairs(SecretVault.PubSub) do + local message = { + Target = subscriber, + Action = action .. " - Notification", + Path = path, + Data = newController and ("New controller added: " .. newController) or ("State changed at path: " .. path) + } + ao.Send(message) + end +end + +-- Notify owner of unauthorized access attempts +local function notifyUnauthorized(action, from) + local message = { + Target = ao.env.process.Owner, + Action = "Unauthorized Attempt - Notification", + Data = "Unauthorized attempt to perform action: " .. action .. " by: " .. from + } + ao.Send(message) +end + +Handlers.add( + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.set), + function(msg) + if msg.From ~= ao.env.process.Owner then + notifyUnauthorized(SecretVault.actions.set, msg.From) + end + assert(msg.From == ao.env.process.Owner, "unauthorized") + assert(msg.Path, "No Path provided") + assert(msg.Value, "No Value provided") + + setNestedValue(SecretVault.State, msg.Path, msg.Value) + + local message = { + Target = msg.From, + Action = actions.set .. " - Notification", + Path = msg.Path, + Value = msg.Value, + Data = "Value set successfully" + } + ao.Send(message) + notifyPubSub(SecretVault.actions.set, msg.Path) + end +) + +Handlers.add( + Handlers.utils.hasMatchingTag('Action', SecretVault.actions.get), + function(msg) + local isAuthorized = SecretVault.authorized(msg.From) + if not isAuthorized then + notifyUnauthorized(SecretVault.actions.get, msg.From) + end + assert(isAuthorized, "unauthorized") + + local current = SecretVault.State + if msg.Path then + for segment in string.gmatch(msg.Path, "[^%.]+") do + current = current[segment] + assert(current, "Path not found") + end + end + + local data = json.encode(current) + + local message = { + Target = msg.From, + Action = actions.get .. " - Notification", + Path = msg.Path, + Value = data, + Data = data + } + ao.Send(message) + end +) + +Handlers.add( + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addController), + function(msg) + if msg.From ~= ao.env.process.Owner then + notifyUnauthorized(SecretVault.actions.addController, msg.From) + end + assert(msg.From == ao.env.process.Owner, "unauthorized") + assert(msg["New-User"], "New User not provided") + assert(SecretVault.authorized(msg['New-User']) == false, "Already authorized") + table.insert(SecretVault.Controllers, msg["New-User"]) + + local message = { + Target = msg.From, + Action = actions.addController .. " - Notification", + ["New-User"] = msg["New-User"], + Data = "User " .. msg["New-User"] .. " added successfully as a controller" + } + ao.Send(message) + ao.Send({ + Target = msg["New-User"], + Action = SecretVault.actions.addController .. " - Notification", + Data = "You have been added as an authorized controller for SecretVault" + }) + notifyPubSub(SecretVault.actions.addController, nil, msg["New-User"]) + end +) + +Handlers.add( + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addPubSub), + function(msg) + if msg.From ~= ao.env.process.Owner then + notifyUnauthorized(SecretVault.actions.addPubSub, msg.From) + end + assert(msg.From == ao.env.process.Owner, "unauthorized") + assert(msg["New-User"], "New User not provided") + table.insert(SecretVault.PubSub, msg["New-User"]) + + local message = { + Target = msg.From, + Action = SecretVault.actions.addPubSub .. " - Notification", + ["New-User"] = msg["New-User"], + Data = "User " .. msg["New-User"] .. " added successfully to PubSub" + } + ao.Send(message) + ao.Send({ + Target = msg["New-User"], + Action = SecretVault.actions.addPubSub .. " - Notification", + Data = "You have been added as a PubSub user for SecretVault" + }) + end +) From af2e044e1de769a379c0954324bb3e7428ac1315 Mon Sep 17 00:00:00 2001 From: bobinstein Date: Tue, 29 Oct 2024 18:23:41 -0400 Subject: [PATCH 2/9] chore: Updated vault for export --- processes/vault.lua | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/processes/vault.lua b/processes/vault.lua index f92ee24..135ca10 100644 --- a/processes/vault.lua +++ b/processes/vault.lua @@ -22,7 +22,7 @@ function SecretVault.authorized(From) end -- Set value in nested table based on dot-separated path -local function setNestedValue(table, path, value) +function SecretVault.setNestedValue(table, path, value) local current = table for segment in string.gmatch(path, "[^%.]+") do if not current[segment] then @@ -34,7 +34,7 @@ local function setNestedValue(table, path, value) end -- Notify all PubSub users of a change -local function notifyPubSub(action, path, newController) +function SecretVault.notifyPubSub(action, path, newController) for _, subscriber in ipairs(SecretVault.PubSub) do local message = { Target = subscriber, @@ -47,7 +47,7 @@ local function notifyPubSub(action, path, newController) end -- Notify owner of unauthorized access attempts -local function notifyUnauthorized(action, from) +function SecretVault.notifyUnauthorized(action, from) local message = { Target = ao.env.process.Owner, Action = "Unauthorized Attempt - Notification", @@ -60,23 +60,23 @@ Handlers.add( Handlers.utils.hasMatchingTag("Action", SecretVault.actions.set), function(msg) if msg.From ~= ao.env.process.Owner then - notifyUnauthorized(SecretVault.actions.set, msg.From) + SecretVault.notifyUnauthorized(SecretVault.actions.set, msg.From) end assert(msg.From == ao.env.process.Owner, "unauthorized") assert(msg.Path, "No Path provided") assert(msg.Value, "No Value provided") - setNestedValue(SecretVault.State, msg.Path, msg.Value) + SecretVault.setNestedValue(SecretVault.State, msg.Path, msg.Value) local message = { Target = msg.From, - Action = actions.set .. " - Notification", + Action = SecretVault.actions.set .. " - Notification", Path = msg.Path, Value = msg.Value, Data = "Value set successfully" } ao.Send(message) - notifyPubSub(SecretVault.actions.set, msg.Path) + SecretVault.notifyPubSub(SecretVault.actions.set, msg.Path) end ) @@ -85,7 +85,7 @@ Handlers.add( function(msg) local isAuthorized = SecretVault.authorized(msg.From) if not isAuthorized then - notifyUnauthorized(SecretVault.actions.get, msg.From) + SecretVault.notifyUnauthorized(SecretVault.actions.get, msg.From) end assert(isAuthorized, "unauthorized") @@ -101,7 +101,7 @@ Handlers.add( local message = { Target = msg.From, - Action = actions.get .. " - Notification", + Action = SecretVault.actions.get .. " - Notification", Path = msg.Path, Value = data, Data = data @@ -114,7 +114,7 @@ Handlers.add( Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addController), function(msg) if msg.From ~= ao.env.process.Owner then - notifyUnauthorized(SecretVault.actions.addController, msg.From) + SecretVault.notifyUnauthorized(SecretVault.actions.addController, msg.From) end assert(msg.From == ao.env.process.Owner, "unauthorized") assert(msg["New-User"], "New User not provided") @@ -123,7 +123,7 @@ Handlers.add( local message = { Target = msg.From, - Action = actions.addController .. " - Notification", + Action = SecretVault.actions.addController .. " - Notification", ["New-User"] = msg["New-User"], Data = "User " .. msg["New-User"] .. " added successfully as a controller" } @@ -133,7 +133,7 @@ Handlers.add( Action = SecretVault.actions.addController .. " - Notification", Data = "You have been added as an authorized controller for SecretVault" }) - notifyPubSub(SecretVault.actions.addController, nil, msg["New-User"]) + SecretVault.notifyPubSub(SecretVault.actions.addController, nil, msg["New-User"]) end ) @@ -141,7 +141,7 @@ Handlers.add( Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addPubSub), function(msg) if msg.From ~= ao.env.process.Owner then - notifyUnauthorized(SecretVault.actions.addPubSub, msg.From) + SecretVault.notifyUnauthorized(SecretVault.actions.addPubSub, msg.From) end assert(msg.From == ao.env.process.Owner, "unauthorized") assert(msg["New-User"], "New User not provided") @@ -161,3 +161,6 @@ Handlers.add( }) end ) + + +return SecretVault \ No newline at end of file From 0511bb4939e4d352fdcd123e68179c4b0addf404 Mon Sep 17 00:00:00 2001 From: bobinstein Date: Tue, 29 Oct 2024 19:03:42 -0400 Subject: [PATCH 3/9] feat: Added SecretVault.Init --- processes/registry.lua | 7 ++ processes/vault.lua | 199 ++++++++++++++++++++--------------------- 2 files changed, 105 insertions(+), 101 deletions(-) create mode 100644 processes/registry.lua diff --git a/processes/registry.lua b/processes/registry.lua new file mode 100644 index 0000000..2c55a6e --- /dev/null +++ b/processes/registry.lua @@ -0,0 +1,7 @@ +local json = require('json') +local SecretVault = require('.vault') + +local actions = { + spawnVault = "Secretorium.Spawn-Vault" +} + diff --git a/processes/vault.lua b/processes/vault.lua index 135ca10..9eef139 100644 --- a/processes/vault.lua +++ b/processes/vault.lua @@ -2,8 +2,8 @@ local json = require('json') SecretVault = SecretVault or {} SecretVault.State = SecretVault.State or {} -SecretVault.PubSub = SecretVault.PubSub or { ao.env.process.Owner } -SecretVault.Controllers = SecretVault.Controllers or { ao.env.process.Owner } +SecretVault.PubSub = SecretVault.PubSub or { ao.env.Process.Owner } +SecretVault.Controllers = SecretVault.Controllers or { ao.env.Process.Owner } SecretVault.actions = { set = 'Set', @@ -49,118 +49,115 @@ end -- Notify owner of unauthorized access attempts function SecretVault.notifyUnauthorized(action, from) local message = { - Target = ao.env.process.Owner, + Target = ao.env.Process.Owner, Action = "Unauthorized Attempt - Notification", Data = "Unauthorized attempt to perform action: " .. action .. " by: " .. from } ao.Send(message) end -Handlers.add( - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.set), - function(msg) - if msg.From ~= ao.env.process.Owner then - SecretVault.notifyUnauthorized(SecretVault.actions.set, msg.From) +function SecretVault.Init() + Handlers.add( + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.set), + function(msg) + local isAuthorized = SecretVault.authorized(msg.From) + if not isAuthorized then + SecretVault.notifyUnauthorized(SecretVault.actions.set, msg.From) + end + assert(isAuthorized, "unauthorized") + assert(msg.Path, "No Path provided") + assert(msg.Value, "No Value provided") + + SecretVault.setNestedValue(SecretVault.State, msg.Path, msg.Value) + + local message = { + Target = msg.From, + Action = SecretVault.actions.set .. " - Notification", + Path = msg.Path, + Value = msg.Value, + Data = "Value set successfully" + } + ao.Send(message) + SecretVault.notifyPubSub(SecretVault.actions.set, msg.Path) end - assert(msg.From == ao.env.process.Owner, "unauthorized") - assert(msg.Path, "No Path provided") - assert(msg.Value, "No Value provided") + ) + + Handlers.add( + Handlers.utils.hasMatchingTag('Action', SecretVault.actions.get), + function(msg) + local current = SecretVault.State + if msg.Path then + for segment in string.gmatch(msg.Path, "[^%.]+") do + current = current[segment] + assert(current, "Path not found") + end + end - SecretVault.setNestedValue(SecretVault.State, msg.Path, msg.Value) + local data = json.encode(current) - local message = { - Target = msg.From, - Action = SecretVault.actions.set .. " - Notification", - Path = msg.Path, - Value = msg.Value, - Data = "Value set successfully" - } - ao.Send(message) - SecretVault.notifyPubSub(SecretVault.actions.set, msg.Path) - end -) - -Handlers.add( - Handlers.utils.hasMatchingTag('Action', SecretVault.actions.get), - function(msg) - local isAuthorized = SecretVault.authorized(msg.From) - if not isAuthorized then - SecretVault.notifyUnauthorized(SecretVault.actions.get, msg.From) + local message = { + Target = msg.From, + Action = SecretVault.actions.get .. " - Notification", + Path = msg.Path, + Value = data, + Data = data + } + ao.Send(message) end - assert(isAuthorized, "unauthorized") + ) - local current = SecretVault.State - if msg.Path then - for segment in string.gmatch(msg.Path, "[^%.]+") do - current = current[segment] - assert(current, "Path not found") + Handlers.add( + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addController), + function(msg) + if msg.From ~= ao.env.Process.Owner then + SecretVault.notifyUnauthorized(SecretVault.actions.addController, msg.From) end + assert(msg.From == ao.env.Process.Owner, "unauthorized") + assert(msg["New-User"], "New User not provided") + assert(SecretVault.authorized(msg['New-User']) == false, "Already authorized") + table.insert(SecretVault.Controllers, msg["New-User"]) + + local message = { + Target = msg.From, + Action = SecretVault.actions.addController .. " - Notification", + ["New-User"] = msg["New-User"], + Data = "User " .. msg["New-User"] .. " added successfully as a controller" + } + ao.Send(message) + ao.Send({ + Target = msg["New-User"], + Action = SecretVault.actions.addController .. " - Notification", + Data = "You have been added as an authorized controller for SecretVault" + }) + SecretVault.notifyPubSub(SecretVault.actions.addController, nil, msg["New-User"]) end + ) - local data = json.encode(current) - - local message = { - Target = msg.From, - Action = SecretVault.actions.get .. " - Notification", - Path = msg.Path, - Value = data, - Data = data - } - ao.Send(message) - end -) - -Handlers.add( - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addController), - function(msg) - if msg.From ~= ao.env.process.Owner then - SecretVault.notifyUnauthorized(SecretVault.actions.addController, msg.From) - end - assert(msg.From == ao.env.process.Owner, "unauthorized") - assert(msg["New-User"], "New User not provided") - assert(SecretVault.authorized(msg['New-User']) == false, "Already authorized") - table.insert(SecretVault.Controllers, msg["New-User"]) - - local message = { - Target = msg.From, - Action = SecretVault.actions.addController .. " - Notification", - ["New-User"] = msg["New-User"], - Data = "User " .. msg["New-User"] .. " added successfully as a controller" - } - ao.Send(message) - ao.Send({ - Target = msg["New-User"], - Action = SecretVault.actions.addController .. " - Notification", - Data = "You have been added as an authorized controller for SecretVault" - }) - SecretVault.notifyPubSub(SecretVault.actions.addController, nil, msg["New-User"]) - end -) - -Handlers.add( - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addPubSub), - function(msg) - if msg.From ~= ao.env.process.Owner then - SecretVault.notifyUnauthorized(SecretVault.actions.addPubSub, msg.From) + Handlers.add( + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addPubSub), + function(msg) + if msg.From ~= ao.env.Process.Owner then + SecretVault.notifyUnauthorized(SecretVault.actions.addPubSub, msg.From) + end + assert(msg.From == ao.env.Process.Owner, "unauthorized") + assert(msg["New-User"], "New User not provided") + table.insert(SecretVault.PubSub, msg["New-User"]) + + local message = { + Target = msg.From, + Action = SecretVault.actions.addPubSub .. " - Notification", + ["New-User"] = msg["New-User"], + Data = "User " .. msg["New-User"] .. " added successfully to PubSub" + } + ao.Send(message) + ao.Send({ + Target = msg["New-User"], + Action = SecretVault.actions.addPubSub .. " - Notification", + Data = "You have been added as a PubSub user for SecretVault" + }) end - assert(msg.From == ao.env.process.Owner, "unauthorized") - assert(msg["New-User"], "New User not provided") - table.insert(SecretVault.PubSub, msg["New-User"]) - - local message = { - Target = msg.From, - Action = SecretVault.actions.addPubSub .. " - Notification", - ["New-User"] = msg["New-User"], - Data = "User " .. msg["New-User"] .. " added successfully to PubSub" - } - ao.Send(message) - ao.Send({ - Target = msg["New-User"], - Action = SecretVault.actions.addPubSub .. " - Notification", - Data = "You have been added as a PubSub user for SecretVault" - }) - end -) - + ) +end -return SecretVault \ No newline at end of file +SecretVault.Init() +return SecretVault From 44cba317a9df7bcdc11e30d31a2e3eea2649ff0d Mon Sep 17 00:00:00 2001 From: bobinstein Date: Thu, 31 Oct 2024 15:48:11 -0400 Subject: [PATCH 4/9] feat: registry --- .gitignore | 1 + dist/registry/aos-bundled.lua | 327 ++++ dist/vault/aos-bundled.lua | 188 ++ package.json | 20 + processes/registry.lua | 120 +- processes/vault.lua | 229 ++- processes/vaultString.lua | 196 ++ tools/build-aos-flavor.js | 176 ++ tools/bundle-aos.js | 36 + tools/constants.js | 71 + ...JDuhwGUsFca1rgfE7ehAKSdgOqPj6aYYy5u4s.wasm | Bin 0 -> 746293 bytes ...rBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk.wasm | Bin 0 -> 731229 bytes tools/lua-bundler.js | 113 ++ tools/publish-aos.js | 34 + tools/scripts/install-deps.js | 22 + tools/spawn-aos.js | 53 + tools/utils.js | 45 + .../aos-process/.gitignore | 7 + .../aos-process/README.md | 21 + .../aos-process/ao.lua | 296 +++ .../aos-process/assignment.lua | 98 + .../aos-process/base64.lua | 201 ++ .../aos-process/bint.lua | 1739 +++++++++++++++++ .../aos-process/chance.lua | 86 + .../aos-process/crypto.md | 852 ++++++++ .../aos-process/crypto/cipher/aes.lua | 142 ++ .../aos-process/crypto/cipher/aes128.lua | 415 ++++ .../aos-process/crypto/cipher/aes192.lua | 462 +++++ .../aos-process/crypto/cipher/aes256.lua | 498 +++++ .../aos-process/crypto/cipher/init.lua | 14 + .../aos-process/crypto/cipher/issac.lua | 204 ++ .../aos-process/crypto/cipher/mode/cbc.lua | 171 ++ .../aos-process/crypto/cipher/mode/cfb.lua | 171 ++ .../aos-process/crypto/cipher/mode/ctr.lua | 252 +++ .../aos-process/crypto/cipher/mode/ecb.lua | 156 ++ .../aos-process/crypto/cipher/mode/ofb.lua | 172 ++ .../aos-process/crypto/cipher/morus.lua | 398 ++++ .../aos-process/crypto/cipher/norx.lua | 321 +++ .../aos-process/crypto/digest/blake2b.lua | 190 ++ .../aos-process/crypto/digest/init.lua | 29 + .../aos-process/crypto/digest/md2.lua | 112 ++ .../aos-process/crypto/digest/md4.lua | 193 ++ .../aos-process/crypto/digest/md5.lua | 178 ++ .../aos-process/crypto/digest/sha1.lua | 191 ++ .../aos-process/crypto/digest/sha2_256.lua | 230 +++ .../aos-process/crypto/digest/sha2_512.lua | 107 + .../aos-process/crypto/digest/sha3.lua | 235 +++ .../aos-process/crypto/init.lua | 17 + .../aos-process/crypto/kdf/init.lua | 8 + .../aos-process/crypto/kdf/pbkdf2.lua | 159 ++ .../aos-process/crypto/mac/hmac.lua | 126 ++ .../aos-process/crypto/mac/init.lua | 8 + .../aos-process/crypto/padding/zero.lua | 17 + .../aos-process/crypto/util/array.lua | 222 +++ .../aos-process/crypto/util/bit.lua | 44 + .../aos-process/crypto/util/hex.lua | 46 + .../aos-process/crypto/util/init.lua | 16 + .../aos-process/crypto/util/queue.lua | 47 + .../aos-process/crypto/util/stream.lua | 98 + .../aos-process/default.lua | 22 + .../aos-process/dump.lua | 297 +++ .../aos-process/eval.lua | 38 + .../aos-process/handlers-utils.lua | 59 + .../aos-process/handlers-utils.md | 138 ++ .../aos-process/handlers.lua | 302 +++ .../aos-process/handlers.md | 102 + .../aos-process/package.json | 16 + .../aos-process/pretty.lua | 20 + .../aos-process/process.lua | 371 ++++ .../aos-process/process.md | 87 + .../aos-process/repl.js | 81 + .../aos-process/stringify.lua | 79 + .../aos-process/test/assignment.test.js | 477 +++++ .../test/crypto/cipher/aes.test.js | 95 + .../test/crypto/cipher/issac.test.js | 52 + .../test/crypto/cipher/morus.test.js | 71 + .../test/crypto/cipher/norx.test.js | 69 + .../test/crypto/digest/blake2b.test.js | 50 + .../test/crypto/digest/md2.test.js | 55 + .../test/crypto/digest/md4.test.js | 53 + .../test/crypto/digest/md5.test.js | 53 + .../test/crypto/digest/sha1.test.js | 52 + .../test/crypto/digest/sha2.test.js | 50 + .../test/crypto/digest/sha3.test.js | 51 + .../test/crypto/kdf/pbkdf2.test.js | 48 + .../aos-process/test/crypto/mac/hmac.test.js | 50 + .../aos-process/test/crypto/random.test.js | 37 + .../aos-process/test/eval.test.js | 107 + .../aos-process/test/handlers.test.js | 332 ++++ .../aos-process/test/inbox.test.js | 51 + .../aos-process/test/magic-table.test.js | 65 + .../aos-process/test/print.test.js | 124 ++ .../aos-process/test/random.test.js | 31 + .../aos-process/test/state.test.js | 63 + .../aos-process/utils.lua | 261 +++ .../aos-process/utils.md | 89 + yarn-error.log | 309 +++ yarn.lock | 259 +++ 98 files changed, 15041 insertions(+), 105 deletions(-) create mode 100644 .gitignore create mode 100644 dist/registry/aos-bundled.lua create mode 100644 dist/vault/aos-bundled.lua create mode 100644 package.json create mode 100644 processes/vaultString.lua create mode 100644 tools/build-aos-flavor.js create mode 100644 tools/bundle-aos.js create mode 100644 tools/constants.js create mode 100644 tools/fixtures/aos-C61NgrJDuhwGUsFca1rgfE7ehAKSdgOqPj6aYYy5u4s.wasm create mode 100644 tools/fixtures/aos-cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk.wasm create mode 100644 tools/lua-bundler.js create mode 100644 tools/publish-aos.js create mode 100644 tools/scripts/install-deps.js create mode 100644 tools/spawn-aos.js create mode 100644 tools/utils.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/.gitignore create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/README.md create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/ao.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/assignment.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/base64.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/bint.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/chance.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto.md create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes128.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes192.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes256.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/init.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/issac.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cbc.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cfb.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ctr.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ecb.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ofb.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/morus.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/norx.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/blake2b.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/init.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md2.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md4.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md5.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha1.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_256.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_512.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha3.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/init.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/kdf/init.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/kdf/pbkdf2.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/hmac.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/init.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/padding/zero.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/array.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/bit.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/hex.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/init.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/queue.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/stream.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/default.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/dump.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/eval.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.md create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.md create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/package.json create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/pretty.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.md create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/repl.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/stringify.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/assignment.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/aes.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/issac.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/morus.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/norx.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/blake2b.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md2.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md4.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md5.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha1.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha2.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha3.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/kdf/pbkdf2.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/mac/hmac.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/random.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/eval.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/handlers.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/inbox.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/magic-table.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/print.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/random.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/state.test.js create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.lua create mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.md create mode 100644 yarn-error.log create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/dist/registry/aos-bundled.lua b/dist/registry/aos-bundled.lua new file mode 100644 index 0000000..d58066d --- /dev/null +++ b/dist/registry/aos-bundled.lua @@ -0,0 +1,327 @@ + + +-- module: ".vaultString" +local function _loaded_mod_vaultString() +vaultString = [=[ + + + + +local json = require('json') + +SecretVault = SecretVault or {} +SecretVault.State = SecretVault.State or {} +SecretVault.PubSub = SecretVault.PubSub or { Owner, ao.env.Process.Tags['Secretorium-Registry'] } +SecretVault.Controllers = SecretVault.Controllers or { Owner } + +SecretVault.actions = { + set = 'Set', + get = 'Get', + addController = "Add-Controller", + addPubSub = "Add-PubSub" +} + +function SecretVault.authorized(From) + for _, value in ipairs(SecretVault.Controllers) do + if value == From then + return true + end + end + return false +end + +-- Set value in nested table based on dot-separated path +function SecretVault.setNestedValue(table, path, value) + local current = table + for segment in string.gmatch(path, "[^%.]+") do + if not current[segment] then + current[segment] = {} + end + current = current[segment] + end + current = value +end + +-- Notify all PubSub users of a change +function SecretVault.notifyPubSub(action, path, newController) + local data + if path then + data = "State change at path: " .. path + else + if newController then + data = "New Controller added: " .. newController + end + end + for _, subscriber in ipairs(SecretVault.PubSub) do + local message = { + Target = subscriber, + Action = action .. "-Notification", + Path = path, + ['New-Controller'] = newController, + Data = data + } + Send(message) + end +end + +-- Notify owner of unauthorized access attempts +function SecretVault.notifyUnauthorized(action, from) + local message = { + Target = Owner, + Action = "Unauthorized Attempt-Notification", + Data = "Unauthorized attempt to perform action: " .. action .. " by: " .. from + } + Send(message) +end + +Handlers.add( + "Info", + Handlers.utils.hasMatchingTag("Action", "Info"), + function(msg) + Send({ + Target = msg.From, + Action = "Info", + Data = "Owner is: " .. Owner + }) + end +) + +Handlers.add( + "Set", + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.set), + function(msg) + local isAuthorized = SecretVault.authorized(msg.From) + if not isAuthorized then + SecretVault.notifyUnauthorized(SecretVault.actions.set, msg.From) + end + assert(isAuthorized, "unauthorized") + assert(msg.Path, "No Path provided") + assert(msg.Value, "No Value provided") + + SecretVault.setNestedValue(SecretVault.State, msg.Path, msg.Value) + + local message = { + Target = msg.From, + Action = SecretVault.actions.set .. "-Notification", + Path = msg.Path, + Value = msg.Value, + Data = "Value set successfully" + } + Send(message) + SecretVault.notifyPubSub(SecretVault.actions.set, msg.Path) + end +) + +Handlers.add( + "Get", + Handlers.utils.hasMatchingTag('Action', SecretVault.actions.get), + function(msg) + local current = SecretVault.State + if msg.Path then + for segment in string.gmatch(msg.Path, "[^%.]+") do + current = current[segment] + assert(current, "Path not found") + end + end + + local data = json.encode(current) + + local message = { + Target = msg.From, + Action = SecretVault.actions.get .. "-Notification", + Path = msg.Path, + Value = data, + Data = data + } + Send(message) + end +) + +Handlers.add( + "Add-Controller", + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addController), + function(msg) + if msg.From ~= Owner then + SecretVault.notifyUnauthorized(SecretVault.actions.addController, msg.From) + end + assert(msg.From == Owner, "unauthorized") + assert(msg["New-Controller"], "New User not provided") + assert(SecretVault.authorized(msg['New-Controller']) == false, "Already authorized") + table.insert(SecretVault.Controllers, msg["New-Controller"]) + + local message = { + Target = msg.From, + Action = SecretVault.actions.addController .. "-Notification", + ["New-Controller"] = msg["New-Controller"], + Data = "User " .. msg["New-Controller"] .. " added successfully as a controller" + } + Send(message) + Send({ + Target = msg["New-Controller"], + Action = SecretVault.actions.addController .. "-Notification", + Data = "You have been added as an authorized controller for SecretVault" + }) + SecretVault.notifyPubSub(SecretVault.actions.addController, nil, msg["New-Controller"]) + end +) + +Handlers.add( + "Add-PubSub", + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addPubSub), + function(msg) + if msg.From ~= Owner then + SecretVault.notifyUnauthorized(SecretVault.actions.addPubSub, msg.From) + end + assert(msg.From == Owner, "unauthorized") + assert(msg["New-Controller"], "New User not provided") + table.insert(SecretVault.PubSub, msg["New-Controller"]) + + local message = { + Target = msg.From, + Action = SecretVault.actions.addPubSub .. "-Notification", + ["New-Controller"] = msg["New-Controller"], + Data = "User " .. msg["New-Controller"] .. " added successfully to PubSub" + } + Send(message) + Send({ + Target = msg["New-Controller"], + Action = SecretVault.actions.addPubSub .. "-Notification", + Data = "You have been added as a PubSub user for SecretVault" + }) + end +) + + +return SecretVault + + +]=] + +return vaultString +end + +_G.package.loaded[".vaultString"] = _loaded_mod_vaultString() + +local json = require('json') +SecretVault = require('.vaultString') + +local actions = { + spawnVault = "Secretorium.Spawn-Vault", + getVault = "Secretorium.Get-Vault", + getMyVaults = "Secretorium.Get-My-Vaults", + addController = "Add-Controller-Notification" +} + +UserVaultList = UserVaultList or {} +VaultList = VaultList or {} + +local defaultVaultName = "" + +Handlers.add( + "spawn", + Handlers.utils.hasMatchingTag("Action", actions.spawnVault), + function(msg) + + local newVault = Spawn(ao.env.Module.Id, { + Tags = { + ['Secretorium-Registry'] = ao.id, + ['Vault-Name'] = msg['Vault-Name'] or defaultVaultName, + ['Creator'] = msg.From, + ['Id'] = msg['Id'], + ['Authority'] = ao.authorities[1] + } + }).receive({ ['Action'] = 'Spawned' }) + + print("Process = " .. newVault.Process) + print("printing again") + -- print(newVault) + local loadResult = Send({ + Target = newVault.Process, + Action = "Eval", + Data = "Owner = '" .. msg.From .. "' " .. SecretVault + -- Data = SecretVault + }) + + UserVaultList[msg.From] = UserVaultList[msg.From] or {} + UserVaultList[msg.From]["Owner"] = UserVaultList[msg.From]["Owner"] or {} + table.insert(UserVaultList[msg.From]["Owner"], newVault.Process) + + VaultList[newVault.Process] = { + Creator = msg.From, + Controllers = {}, + CreatedAt = msg['Block-Height'], + ['Vault-Name'] = msg['Vault-Name'] or defaultVaultName + } + + Send({ + Target = msg.From, + Action = actions.spawnVault .. "-Notification", + Data = newVault.Process + }) + end +) + + +Handlers.add( + "Add-Controller", + Handlers.utils.hasMatchingTag("Action", actions.addController), + function(msg) + assert(VaultList[msg.From], "Unauthorized") + assert(msg['New-Controller'], "Invalid input, must include new controller") + + UserVaultList[msg['New-Controller']] = UserVaultList[msg['New-Controller']] or {} + UserVaultList[msg['New-Controller']]['Controller'] = UserVaultList[msg['New-Controller']]['Controller'] or {} + table.insert(UserVaultList[msg['New-Controller']]["Controller"], msg.From ) + + table.insert(VaultList[msg.From]['Controllers'], msg['New-Controller']) + end +) + +Handlers.add( + "Get-Vault", + Handlers.utils.hasMatchingTag('Action', actions.getVault), + function(msg) + -- Ensure vault ID is provided and valid + assert(msg['Vault-Id'], "Invalid Input") + assert(VaultList[msg['Vault-Id']], "Vault not found") + + -- Authorization check + local vault = VaultList[msg['Vault-Id']] + local authorized = (vault.Creator == msg.From) + + -- Check if msg.From is in the list of controllers + if not authorized then + for _, controller in ipairs(vault.Controllers or {}) do + if controller == msg.From then + authorized = true + break + end + end + end + + -- Throw unauthorized error if not authorized + assert(authorized, "Unauthorized") + + Send({ + Target = msg.From, + Action = actions.getVault .. "-Notification", + Data = json.encode(vault) + }) + end +) + + +Handlers.add( + 'Get-My-Vaults', + Handlers.utils.hasMatchingTag('Action', actions.getMyVaults), + function(msg) + assert(UserVaultList[msg.From], "No vaults found") + + Send({ + Target = msg.From, + Action = actions.getMyVaults .. "-Notification", + Data = json.encode(UserVaultList[msg.From]) + }) + + end +) \ No newline at end of file diff --git a/dist/vault/aos-bundled.lua b/dist/vault/aos-bundled.lua new file mode 100644 index 0000000..d488989 --- /dev/null +++ b/dist/vault/aos-bundled.lua @@ -0,0 +1,188 @@ + + +local json = require('json') + +SecretVault = SecretVault or {} +SecretVault.State = SecretVault.State or {} +SecretVault.PubSub = SecretVault.PubSub or { Owner, ao.env.Process.Tags['Secretorium-Registry'] } +SecretVault.Controllers = SecretVault.Controllers or { Owner } + +SecretVault.actions = { + set = 'Set', + get = 'Get', + addController = "Add-Controller", + addPubSub = "Add-PubSub" +} + +function SecretVault.authorized(From) + for _, value in ipairs(SecretVault.Controllers) do + if value == From then + return true + end + end + return false +end + +-- Set value in nested table based on dot-separated path +function SecretVault.setNestedValue(table, path, value) + local current = table + for segment in string.gmatch(path, "[^%.]+") do + if not current[segment] then + current[segment] = {} + end + current = current[segment] + end + current = value +end + +-- Notify all PubSub users of a change +function SecretVault.notifyPubSub(action, path, newController) + local data + if path then + data = "State change at path: " .. path + else + if newController then + data = "New Controller added: " .. newController + end + end + for _, subscriber in ipairs(SecretVault.PubSub) do + local message = { + Target = subscriber, + Action = action .. "-Notification", + Path = path, + ['New-Controller'] = newController, + Data = data + } + Send(message) + end +end + +-- Notify owner of unauthorized access attempts +function SecretVault.notifyUnauthorized(action, from) + local message = { + Target = Owner, + Action = "Unauthorized Attempt-Notification", + Data = "Unauthorized attempt to perform action: " .. action .. " by: " .. from + } + Send(message) +end + +Handlers.add( + "Info", + Handlers.utils.hasMatchingTag("Action", "Info"), + function(msg) + Send({ + Target = msg.From, + Action = "Info", + Data = "Owner is: " .. Owner + }) + end +) + +Handlers.add( + "Set", + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.set), + function(msg) + local isAuthorized = SecretVault.authorized(msg.From) + if not isAuthorized then + SecretVault.notifyUnauthorized(SecretVault.actions.set, msg.From) + end + assert(isAuthorized, "unauthorized") + assert(msg.Path, "No Path provided") + assert(msg.Value, "No Value provided") + + SecretVault.setNestedValue(SecretVault.State, msg.Path, msg.Value) + + local message = { + Target = msg.From, + Action = SecretVault.actions.set .. "-Notification", + Path = msg.Path, + Value = msg.Value, + Data = "Value set successfully" + } + Send(message) + SecretVault.notifyPubSub(SecretVault.actions.set, msg.Path) + end +) + +Handlers.add( + "Get", + Handlers.utils.hasMatchingTag('Action', SecretVault.actions.get), + function(msg) + local current = SecretVault.State + if msg.Path then + for segment in string.gmatch(msg.Path, "[^%.]+") do + current = current[segment] + assert(current, "Path not found") + end + end + + local data = json.encode(current) + + local message = { + Target = msg.From, + Action = SecretVault.actions.get .. "-Notification", + Path = msg.Path, + Value = data, + Data = data + } + Send(message) + end +) + +Handlers.add( + "Add-Controller", + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addController), + function(msg) + if msg.From ~= Owner then + SecretVault.notifyUnauthorized(SecretVault.actions.addController, msg.From) + end + assert(msg.From == Owner, "unauthorized") + assert(msg["New-Controller"], "New User not provided") + assert(SecretVault.authorized(msg['New-Controller']) == false, "Already authorized") + table.insert(SecretVault.Controllers, msg["New-Controller"]) + + local message = { + Target = msg.From, + Action = SecretVault.actions.addController .. "-Notification", + ["New-Controller"] = msg["New-Controller"], + Data = "User " .. msg["New-Controller"] .. " added successfully as a controller" + } + Send(message) + Send({ + Target = msg["New-Controller"], + Action = SecretVault.actions.addController .. "-Notification", + Data = "You have been added as an authorized controller for SecretVault" + }) + SecretVault.notifyPubSub(SecretVault.actions.addController, nil, msg["New-Controller"]) + end +) + +Handlers.add( + "Add-PubSub", + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addPubSub), + function(msg) + if msg.From ~= Owner then + SecretVault.notifyUnauthorized(SecretVault.actions.addPubSub, msg.From) + end + assert(msg.From == Owner, "unauthorized") + assert(msg["New-Controller"], "New User not provided") + table.insert(SecretVault.PubSub, msg["New-Controller"]) + + local message = { + Target = msg.From, + Action = SecretVault.actions.addPubSub .. "-Notification", + ["New-Controller"] = msg["New-Controller"], + Data = "User " .. msg["New-Controller"] .. " added successfully to PubSub" + } + Send(message) + Send({ + Target = msg["New-Controller"], + Action = SecretVault.actions.addPubSub .. "-Notification", + Data = "You have been added as a PubSub user for SecretVault" + }) + end +) + + +return SecretVault diff --git a/package.json b/package.json new file mode 100644 index 0000000..9846537 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "secretorium", + "version": "1.0.0", + "main": "index.js", + "repository": "https://github.com/project-kardeshev/secretorium", + "author": "bobinstein ", + "license": "MIT", + "type": "module", + "scripts": { + "build:registry": "node tools/bundle-aos.js --path=\"./processes/registry.lua\" --output=\"./dist/registry/aos-bundled.lua\"", + "build:vault": "node tools/bundle-aos.js --path=\"./processes/vault.lua\" --output=\"./dist/vault/aos-bundled.lua\"" + }, + "devDependencies": { + "@permaweb/ao-loader": "^0.0.43", + "@permaweb/aoconnect": "^0.0.59", + "arweave": "^1.15.5", + "fs-extra": "^11.2.0", + "prettier": "^3.3.3" + } +} diff --git a/processes/registry.lua b/processes/registry.lua index 2c55a6e..477431e 100644 --- a/processes/registry.lua +++ b/processes/registry.lua @@ -1,7 +1,123 @@ local json = require('json') -local SecretVault = require('.vault') +SecretVault = require('.vaultString') local actions = { - spawnVault = "Secretorium.Spawn-Vault" + spawnVault = "Secretorium.Spawn-Vault", + getVault = "Secretorium.Get-Vault", + getMyVaults = "Secretorium.Get-My-Vaults", + addController = "Add-Controller-Notification" } +UserVaultList = UserVaultList or {} +VaultList = VaultList or {} + +local defaultVaultName = "" + +Handlers.add( + "spawn", + Handlers.utils.hasMatchingTag("Action", actions.spawnVault), + function(msg) + + local newVault = Spawn(ao.env.Module.Id, { + Tags = { + ['Secretorium-Registry'] = ao.id, + ['Vault-Name'] = msg['Vault-Name'] or defaultVaultName, + ['Creator'] = msg.From, + ['Id'] = msg['Id'], + ['Authority'] = ao.authorities[1] + } + }).receive({ ['Action'] = 'Spawned' }) + + print("Process = " .. newVault.Process) + print("printing again") + -- print(newVault) + local loadResult = Send({ + Target = newVault.Process, + Action = "Eval", + Data = "Owner = '" .. msg.From .. "' " .. SecretVault + -- Data = SecretVault + }) + + UserVaultList[msg.From] = UserVaultList[msg.From] or {} + UserVaultList[msg.From]["Owner"] = UserVaultList[msg.From]["Owner"] or {} + table.insert(UserVaultList[msg.From]["Owner"], newVault.Process) + + VaultList[newVault.Process] = { + Creator = msg.From, + Controllers = {}, + CreatedAt = msg['Block-Height'], + ['Vault-Name'] = msg['Vault-Name'] or defaultVaultName + } + + Send({ + Target = msg.From, + Action = actions.spawnVault .. "-Notification", + Data = newVault.Process + }) + end +) + + +Handlers.add( + "Add-Controller", + Handlers.utils.hasMatchingTag("Action", actions.addController), + function(msg) + assert(VaultList[msg.From], "Unauthorized") + assert(msg['New-Controller'], "Invalid input, must include new controller") + + UserVaultList[msg['New-Controller']] = UserVaultList[msg['New-Controller']] or {} + UserVaultList[msg['New-Controller']]['Controller'] = UserVaultList[msg['New-Controller']]['Controller'] or {} + table.insert(UserVaultList[msg['New-Controller']]["Controller"], msg.From ) + + table.insert(VaultList[msg.From]['Controllers'], msg['New-Controller']) + end +) + +Handlers.add( + "Get-Vault", + Handlers.utils.hasMatchingTag('Action', actions.getVault), + function(msg) + -- Ensure vault ID is provided and valid + assert(msg['Vault-Id'], "Invalid Input") + assert(VaultList[msg['Vault-Id']], "Vault not found") + + -- Authorization check + local vault = VaultList[msg['Vault-Id']] + local authorized = (vault.Creator == msg.From) + + -- Check if msg.From is in the list of controllers + if not authorized then + for _, controller in ipairs(vault.Controllers or {}) do + if controller == msg.From then + authorized = true + break + end + end + end + + -- Throw unauthorized error if not authorized + assert(authorized, "Unauthorized") + + Send({ + Target = msg.From, + Action = actions.getVault .. "-Notification", + Data = json.encode(vault) + }) + end +) + + +Handlers.add( + 'Get-My-Vaults', + Handlers.utils.hasMatchingTag('Action', actions.getMyVaults), + function(msg) + assert(UserVaultList[msg.From], "No vaults found") + + Send({ + Target = msg.From, + Action = actions.getMyVaults .. "-Notification", + Data = json.encode(UserVaultList[msg.From]) + }) + + end +) \ No newline at end of file diff --git a/processes/vault.lua b/processes/vault.lua index 9eef139..cbe4654 100644 --- a/processes/vault.lua +++ b/processes/vault.lua @@ -2,8 +2,8 @@ local json = require('json') SecretVault = SecretVault or {} SecretVault.State = SecretVault.State or {} -SecretVault.PubSub = SecretVault.PubSub or { ao.env.Process.Owner } -SecretVault.Controllers = SecretVault.Controllers or { ao.env.Process.Owner } +SecretVault.PubSub = SecretVault.PubSub or { Owner, ao.env.Process.Tags['Secretorium-Registry'] } +SecretVault.Controllers = SecretVault.Controllers or { Owner } SecretVault.actions = { set = 'Set', @@ -35,129 +35,152 @@ end -- Notify all PubSub users of a change function SecretVault.notifyPubSub(action, path, newController) + local data + if path then + data = "State change at path: " .. path + else + if newController then + data = "New Controller added: " .. newController + end + end for _, subscriber in ipairs(SecretVault.PubSub) do local message = { Target = subscriber, - Action = action .. " - Notification", + Action = action .. "-Notification", Path = path, - Data = newController and ("New controller added: " .. newController) or ("State changed at path: " .. path) + ['New-Controller'] = newController, + Data = data } - ao.Send(message) + Send(message) end end -- Notify owner of unauthorized access attempts function SecretVault.notifyUnauthorized(action, from) local message = { - Target = ao.env.Process.Owner, - Action = "Unauthorized Attempt - Notification", + Target = Owner, + Action = "Unauthorized Attempt-Notification", Data = "Unauthorized attempt to perform action: " .. action .. " by: " .. from } - ao.Send(message) + Send(message) end -function SecretVault.Init() - Handlers.add( - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.set), - function(msg) - local isAuthorized = SecretVault.authorized(msg.From) - if not isAuthorized then - SecretVault.notifyUnauthorized(SecretVault.actions.set, msg.From) - end - assert(isAuthorized, "unauthorized") - assert(msg.Path, "No Path provided") - assert(msg.Value, "No Value provided") - - SecretVault.setNestedValue(SecretVault.State, msg.Path, msg.Value) - - local message = { - Target = msg.From, - Action = SecretVault.actions.set .. " - Notification", - Path = msg.Path, - Value = msg.Value, - Data = "Value set successfully" - } - ao.Send(message) - SecretVault.notifyPubSub(SecretVault.actions.set, msg.Path) +Handlers.add( + "Info", + Handlers.utils.hasMatchingTag("Action", "Info"), + function(msg) + Send({ + Target = msg.From, + Action = "Info", + Data = "Owner is: " .. Owner + }) + end +) + +Handlers.add( + "Set", + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.set), + function(msg) + local isAuthorized = SecretVault.authorized(msg.From) + if not isAuthorized then + SecretVault.notifyUnauthorized(SecretVault.actions.set, msg.From) end - ) - - Handlers.add( - Handlers.utils.hasMatchingTag('Action', SecretVault.actions.get), - function(msg) - local current = SecretVault.State - if msg.Path then - for segment in string.gmatch(msg.Path, "[^%.]+") do - current = current[segment] - assert(current, "Path not found") - end - end + assert(isAuthorized, "unauthorized") + assert(msg.Path, "No Path provided") + assert(msg.Value, "No Value provided") - local data = json.encode(current) + SecretVault.setNestedValue(SecretVault.State, msg.Path, msg.Value) - local message = { - Target = msg.From, - Action = SecretVault.actions.get .. " - Notification", - Path = msg.Path, - Value = data, - Data = data - } - ao.Send(message) + local message = { + Target = msg.From, + Action = SecretVault.actions.set .. "-Notification", + Path = msg.Path, + Value = msg.Value, + Data = "Value set successfully" + } + Send(message) + SecretVault.notifyPubSub(SecretVault.actions.set, msg.Path) + end +) + +Handlers.add( + "Get", + Handlers.utils.hasMatchingTag('Action', SecretVault.actions.get), + function(msg) + local current = SecretVault.State + if msg.Path then + for segment in string.gmatch(msg.Path, "[^%.]+") do + current = current[segment] + assert(current, "Path not found") + end end - ) - Handlers.add( - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addController), - function(msg) - if msg.From ~= ao.env.Process.Owner then - SecretVault.notifyUnauthorized(SecretVault.actions.addController, msg.From) - end - assert(msg.From == ao.env.Process.Owner, "unauthorized") - assert(msg["New-User"], "New User not provided") - assert(SecretVault.authorized(msg['New-User']) == false, "Already authorized") - table.insert(SecretVault.Controllers, msg["New-User"]) - - local message = { - Target = msg.From, - Action = SecretVault.actions.addController .. " - Notification", - ["New-User"] = msg["New-User"], - Data = "User " .. msg["New-User"] .. " added successfully as a controller" - } - ao.Send(message) - ao.Send({ - Target = msg["New-User"], - Action = SecretVault.actions.addController .. " - Notification", - Data = "You have been added as an authorized controller for SecretVault" - }) - SecretVault.notifyPubSub(SecretVault.actions.addController, nil, msg["New-User"]) + local data = json.encode(current) + + local message = { + Target = msg.From, + Action = SecretVault.actions.get .. "-Notification", + Path = msg.Path, + Value = data, + Data = data + } + Send(message) + end +) + +Handlers.add( + "Add-Controller", + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addController), + function(msg) + if msg.From ~= Owner then + SecretVault.notifyUnauthorized(SecretVault.actions.addController, msg.From) end - ) + assert(msg.From == Owner, "unauthorized") + assert(msg["New-Controller"], "New User not provided") + assert(SecretVault.authorized(msg['New-Controller']) == false, "Already authorized") + table.insert(SecretVault.Controllers, msg["New-Controller"]) - Handlers.add( - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addPubSub), - function(msg) - if msg.From ~= ao.env.Process.Owner then - SecretVault.notifyUnauthorized(SecretVault.actions.addPubSub, msg.From) - end - assert(msg.From == ao.env.Process.Owner, "unauthorized") - assert(msg["New-User"], "New User not provided") - table.insert(SecretVault.PubSub, msg["New-User"]) - - local message = { - Target = msg.From, - Action = SecretVault.actions.addPubSub .. " - Notification", - ["New-User"] = msg["New-User"], - Data = "User " .. msg["New-User"] .. " added successfully to PubSub" - } - ao.Send(message) - ao.Send({ - Target = msg["New-User"], - Action = SecretVault.actions.addPubSub .. " - Notification", - Data = "You have been added as a PubSub user for SecretVault" - }) + local message = { + Target = msg.From, + Action = SecretVault.actions.addController .. "-Notification", + ["New-Controller"] = msg["New-Controller"], + Data = "User " .. msg["New-Controller"] .. " added successfully as a controller" + } + Send(message) + Send({ + Target = msg["New-Controller"], + Action = SecretVault.actions.addController .. "-Notification", + Data = "You have been added as an authorized controller for SecretVault" + }) + SecretVault.notifyPubSub(SecretVault.actions.addController, nil, msg["New-Controller"]) + end +) + +Handlers.add( + "Add-PubSub", + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addPubSub), + function(msg) + if msg.From ~= Owner then + SecretVault.notifyUnauthorized(SecretVault.actions.addPubSub, msg.From) end - ) -end + assert(msg.From == Owner, "unauthorized") + assert(msg["New-Controller"], "New User not provided") + table.insert(SecretVault.PubSub, msg["New-Controller"]) + + local message = { + Target = msg.From, + Action = SecretVault.actions.addPubSub .. "-Notification", + ["New-Controller"] = msg["New-Controller"], + Data = "User " .. msg["New-Controller"] .. " added successfully to PubSub" + } + Send(message) + Send({ + Target = msg["New-Controller"], + Action = SecretVault.actions.addPubSub .. "-Notification", + Data = "You have been added as a PubSub user for SecretVault" + }) + end +) + -SecretVault.Init() return SecretVault diff --git a/processes/vaultString.lua b/processes/vaultString.lua new file mode 100644 index 0000000..656bd2a --- /dev/null +++ b/processes/vaultString.lua @@ -0,0 +1,196 @@ +vaultString = [=[ + + + + +local json = require('json') + +SecretVault = SecretVault or {} +SecretVault.State = SecretVault.State or {} +SecretVault.PubSub = SecretVault.PubSub or { Owner, ao.env.Process.Tags['Secretorium-Registry'] } +SecretVault.Controllers = SecretVault.Controllers or { Owner } + +SecretVault.actions = { + set = 'Set', + get = 'Get', + addController = "Add-Controller", + addPubSub = "Add-PubSub" +} + +function SecretVault.authorized(From) + for _, value in ipairs(SecretVault.Controllers) do + if value == From then + return true + end + end + return false +end + +-- Set value in nested table based on dot-separated path +function SecretVault.setNestedValue(table, path, value) + local current = table + for segment in string.gmatch(path, "[^%.]+") do + if not current[segment] then + current[segment] = {} + end + current = current[segment] + end + current = value +end + +-- Notify all PubSub users of a change +function SecretVault.notifyPubSub(action, path, newController) + local data + if path then + data = "State change at path: " .. path + else + if newController then + data = "New Controller added: " .. newController + end + end + for _, subscriber in ipairs(SecretVault.PubSub) do + local message = { + Target = subscriber, + Action = action .. "-Notification", + Path = path, + ['New-Controller'] = newController, + Data = data + } + Send(message) + end +end + +-- Notify owner of unauthorized access attempts +function SecretVault.notifyUnauthorized(action, from) + local message = { + Target = Owner, + Action = "Unauthorized Attempt-Notification", + Data = "Unauthorized attempt to perform action: " .. action .. " by: " .. from + } + Send(message) +end + +Handlers.add( + "Info", + Handlers.utils.hasMatchingTag("Action", "Info"), + function(msg) + Send({ + Target = msg.From, + Action = "Info", + Data = "Owner is: " .. Owner + }) + end +) + +Handlers.add( + "Set", + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.set), + function(msg) + local isAuthorized = SecretVault.authorized(msg.From) + if not isAuthorized then + SecretVault.notifyUnauthorized(SecretVault.actions.set, msg.From) + end + assert(isAuthorized, "unauthorized") + assert(msg.Path, "No Path provided") + assert(msg.Value, "No Value provided") + + SecretVault.setNestedValue(SecretVault.State, msg.Path, msg.Value) + + local message = { + Target = msg.From, + Action = SecretVault.actions.set .. "-Notification", + Path = msg.Path, + Value = msg.Value, + Data = "Value set successfully" + } + Send(message) + SecretVault.notifyPubSub(SecretVault.actions.set, msg.Path) + end +) + +Handlers.add( + "Get", + Handlers.utils.hasMatchingTag('Action', SecretVault.actions.get), + function(msg) + local current = SecretVault.State + if msg.Path then + for segment in string.gmatch(msg.Path, "[^%.]+") do + current = current[segment] + assert(current, "Path not found") + end + end + + local data = json.encode(current) + + local message = { + Target = msg.From, + Action = SecretVault.actions.get .. "-Notification", + Path = msg.Path, + Value = data, + Data = data + } + Send(message) + end +) + +Handlers.add( + "Add-Controller", + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addController), + function(msg) + if msg.From ~= Owner then + SecretVault.notifyUnauthorized(SecretVault.actions.addController, msg.From) + end + assert(msg.From == Owner, "unauthorized") + assert(msg["New-Controller"], "New User not provided") + assert(SecretVault.authorized(msg['New-Controller']) == false, "Already authorized") + table.insert(SecretVault.Controllers, msg["New-Controller"]) + + local message = { + Target = msg.From, + Action = SecretVault.actions.addController .. "-Notification", + ["New-Controller"] = msg["New-Controller"], + Data = "User " .. msg["New-Controller"] .. " added successfully as a controller" + } + Send(message) + Send({ + Target = msg["New-Controller"], + Action = SecretVault.actions.addController .. "-Notification", + Data = "You have been added as an authorized controller for SecretVault" + }) + SecretVault.notifyPubSub(SecretVault.actions.addController, nil, msg["New-Controller"]) + end +) + +Handlers.add( + "Add-PubSub", + Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addPubSub), + function(msg) + if msg.From ~= Owner then + SecretVault.notifyUnauthorized(SecretVault.actions.addPubSub, msg.From) + end + assert(msg.From == Owner, "unauthorized") + assert(msg["New-Controller"], "New User not provided") + table.insert(SecretVault.PubSub, msg["New-Controller"]) + + local message = { + Target = msg.From, + Action = SecretVault.actions.addPubSub .. "-Notification", + ["New-Controller"] = msg["New-Controller"], + Data = "User " .. msg["New-Controller"] .. " added successfully to PubSub" + } + Send(message) + Send({ + Target = msg["New-Controller"], + Action = SecretVault.actions.addPubSub .. "-Notification", + Data = "You have been added as a PubSub user for SecretVault" + }) + end +) + + +return SecretVault + + +]=] + +return vaultString \ No newline at end of file diff --git a/tools/build-aos-flavor.js b/tools/build-aos-flavor.js new file mode 100644 index 0000000..dafc1a8 --- /dev/null +++ b/tools/build-aos-flavor.js @@ -0,0 +1,176 @@ +import { readFileSync, existsSync, mkdirSync, writeFileSync } from "fs"; +import * as fs from "fs-extra"; // Import fs-extra for recursive directory copying +import * as path from "path"; +import { bundle } from "./lua-bundler.js"; +import { fileURLToPath } from "url"; +import { randomUUID } from "crypto"; +import Docker from "dockerode"; +import { exec } from "child_process"; +import util from "util"; + +const execPromise = util.promisify(exec); // Promisify exec for async/await usage +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const docker = new Docker(); // Initialize dockerode instance + +const args = process.argv.slice(2); // Get CLI arguments +const pathArg = args.find((arg) => arg.startsWith("--path=")); +const outputArg = args.find((arg) => arg.startsWith("--output=")); + +if (!pathArg || !outputArg) { + console.error("Please provide both a --path and --output as CLI arguments."); + process.exit(1); +} + +const pathToLua = pathArg.split("=")[1]; +const outputPath = outputArg.split("=")[1]; + +// Create working directory and copy AOS files to it. Set random id to avoid conflicts +const id = randomUUID(); +const workingDir = path.join(__dirname, "working-" + id); +if (!existsSync(workingDir)) { + mkdirSync(workingDir, { recursive: true }); +} + +// Copy the entire aos/process directory to the working directory +const sourceDir = path.join(__dirname, "..", "aos", "process"); +if (!existsSync(sourceDir)) { + await execPromise("git submodule update --init --recursive"); +} +if (!existsSync(sourceDir)) { + console.error( + "AOS process directory not found. Please ensure the submodule has been initialized.", + ); + process.exit(1); +} +const destDir = path.join(workingDir, "aos-process"); +try { + await fs.copy(sourceDir, destDir); + console.log("Successfully copied AOS process directory"); +} catch (err) { + console.error("Error copying AOS process directory:", err); + process.exit(1); +} + +async function checkAndStartDocker() { + try { + // Check if Docker is running + await docker.ping(); + console.log("Docker is running."); + } catch (err) { + console.error("Docker is not running. Attempting to start Docker..."); + + if (process.platform === "darwin") { + try { + // macOS: Start Docker Desktop + console.log("Attempting to start Docker Desktop on macOS..."); + await execPromise("open -a Docker"); + + // Wait for Docker to start + await new Promise((resolve) => setTimeout(resolve, 5000)); + + // Recheck if Docker is running + await docker.ping(); + console.log("Docker has been started and is running."); + } catch (startErr) { + console.error( + "Failed to start Docker on macOS. Please ensure Docker Desktop is installed and running.", + startErr.message, + ); + process.exit(1); + } + } else { + try { + // Linux: Use systemctl to start Docker + console.log("Attempting to start Docker on Linux..."); + await execPromise("sudo systemctl start docker"); + console.log("Docker has been started."); + } catch (startErr) { + console.error( + "Failed to start Docker on Linux. Please ensure Docker is installed and you have permissions to start it.", + startErr.message, + ); + process.exit(1); + } + } + } +} + +async function checkAoCLI() { + try { + // Check if `ao` CLI is installed by running a simple version check + await execPromise("ao --version"); + console.log("ao CLI is available."); + } catch (err) { + // If the ao CLI is not installed, install it with yarn install-deps + console.log( + "ao CLI is not available. Installing dependencies with yarn install-deps...", + ); + try { + await execPromise("yarn install-deps"); + console.log("ao CLI has been installed successfully."); + } catch (installErr) { + console.error( + "Failed to install ao CLI dependencies.", + installErr.message, + ); + process.exit(1); + } + } +} + +async function main() { + // Check if Docker is running and start if needed + await checkAndStartDocker(); + + // Check if ao CLI is installed and install if missing + await checkAoCLI(); + + const bundledLua = bundle(pathToLua); + + // Write bundled Lua to /aos-process/process.lua above "return process" + const processLuaPath = path.join(workingDir, "aos-process", "process.lua"); + const processLua = readFileSync(processLuaPath, "utf8"); + const processLuaParts = processLua.split("return process"); + const newProcessLua = + processLuaParts[0] + + "\n" + + bundledLua + + "\nreturn process" + + processLuaParts[1]; + writeFileSync(processLuaPath, newProcessLua); + + // Build AOS wasm binary + const buildCmd = `cd ${workingDir}/aos-process && ao build`; + console.log("Building AOS binary with command:", buildCmd); + + try { + const { stdout, stderr } = await execPromise(buildCmd); + if (stderr) { + console.error("Build error:", stderr); + } + console.log("Build result:", stdout); + } catch (buildErr) { + console.error("Failed to build AOS binary:", buildErr.message); + process.exit(1); + } + + // Move built wasm (process.wasm) to output path + const wasmPath = path.join(workingDir, "aos-process", "process.wasm"); + const wasmOutputPath = path.join(outputPath, "process.wasm"); + await fs.copy(wasmPath, wasmOutputPath); + console.log("WASM has been built and saved to", wasmOutputPath); + + // Write bundled Lua to output path + const luaOutputPath = path.join(outputPath, "aos-bundled.lua"); + writeFileSync(luaOutputPath, bundledLua); + console.log("Lua has been bundled and saved to", luaOutputPath); +} + +main() + .catch((e) => console.error(e)) + .finally(async () => { + // Cleanup working directory + await fs.remove(workingDir); + process.exit(0); + }); diff --git a/tools/bundle-aos.js b/tools/bundle-aos.js new file mode 100644 index 0000000..0b51871 --- /dev/null +++ b/tools/bundle-aos.js @@ -0,0 +1,36 @@ +import * as fs from "fs"; +import * as path from "path"; +import { bundle } from "./lua-bundler.js"; +import { fileURLToPath } from "url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +async function main() { + const args = process.argv.slice(2); // Get CLI arguments + const pathArg = args.find((arg) => arg.startsWith("--path=")); + const outputArg = args.find((arg) => arg.startsWith("--output=")); + + if (!pathArg || !outputArg) { + console.error( + "Please provide both a --path and --output as CLI arguments.", + ); + return; + } + + const pathToLua = pathArg.split("=")[1]; + const outputPath = outputArg.split("=")[1]; + + console.log("Path to Lua:", pathToLua); + console.log("Output Path:", outputPath); + + const bundledLua = bundle(pathToLua); + + const distDir = path.dirname(outputPath); + if (!fs.existsSync(distDir)) { + fs.mkdirSync(distDir, { recursive: true }); + } + + fs.writeFileSync(outputPath, bundledLua); + console.log("Lua has been bundled and saved to", outputPath); +} + +main(); diff --git a/tools/constants.js b/tools/constants.js new file mode 100644 index 0000000..7a998dd --- /dev/null +++ b/tools/constants.js @@ -0,0 +1,71 @@ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +export const kibibyte = 1024; +export const mebibyte = kibibyte * 1024; +export const gibibyte = mebibyte * 1024; +export const STUB_ADDRESS = "".padEnd(43, "1"); +export const ALTERNATE_STUB_ADDRESS = "".padEnd(43, "2"); +/* ao READ-ONLY Env Variables */ +export const AO_LOADER_HANDLER_ENV = { + Process: { + Id: "".padEnd(43, "1"), + Owner: STUB_ADDRESS, + Tags: [{ name: "Authority", value: "XXXXXX" }], + }, + Module: { + Id: "".padEnd(43, "1"), + Tags: [{ name: "Authority", value: "YYYYYY" }], + }, +}; + +export const AO_LOADER_OPTIONS = { + format: "wasm64-unknown-emscripten-draft_2024_02_15", + inputEncoding: "JSON-1", + outputEncoding: "JSON-1", + memoryLimit: gibibyte, + computeLimit: (9e12).toString(), + extensions: [], +}; + +export const AOS_WASM = fs.readFileSync( + path.join( + __dirname, + "fixtures/aos-C61NgrJDuhwGUsFca1rgfE7ehAKSdgOqPj6aYYy5u4s.wasm", + ), +); + +export const BUNDLED_CHESS_REGISTRY_AOS_LUA = fs.readFileSync( + path.join(__dirname, "../dist/chess/registry/aos-bundled.lua"), + "utf-8", +); + +export const BUNDLED_CHESS_GAME_AOS_LUA = fs.readFileSync( + path.join(__dirname, "../dist/chess/game/aos-bundled.lua"), + "utf-8", +); + +export const DEFAULT_HANDLE_OPTIONS = { + Id: "".padEnd(43, "1"), + ["Block-Height"]: "1", + // important to set the address so that that `Authority` check passes. Else the `isTrusted` with throw an error. + Owner: STUB_ADDRESS, + Module: "ANT", + Target: "".padEnd(43, "1"), + From: STUB_ADDRESS, + Timestamp: Date.now(), +}; + +export const ALTERNATE_HANDLE_OPTIONS = { + Id: "".padEnd(43, "2"), + ["Block-Height"]: "1", + // important to set the address so that that `Authority` check passes. Else the `isTrusted` with throw an error. + Owner: ALTERNATE_STUB_ADDRESS, + Module: "ANT", + Target: "".padEnd(43, "1"), + From: ALTERNATE_STUB_ADDRESS, + Timestamp: Date.now(), +}; diff --git a/tools/fixtures/aos-C61NgrJDuhwGUsFca1rgfE7ehAKSdgOqPj6aYYy5u4s.wasm b/tools/fixtures/aos-C61NgrJDuhwGUsFca1rgfE7ehAKSdgOqPj6aYYy5u4s.wasm new file mode 100644 index 0000000000000000000000000000000000000000..564e51c00e495938c12bb25b5932c48a1861b851 GIT binary patch literal 746293 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 P1LmLYfQ9EeVDbL|V)EMy literal 0 HcmV?d00001 diff --git a/tools/fixtures/aos-cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk.wasm b/tools/fixtures/aos-cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk.wasm new file mode 100644 index 0000000000000000000000000000000000000000..74b9e5a6a5c5fcfd629f0eba199f3e9580141bc9 GIT binary patch literal 731229 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

ghaBp*JT!6XKUzf}=(v*pQ)bCS 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 z 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 BHT3`h literal 0 HcmV?d00001 diff --git a/tools/lua-bundler.js b/tools/lua-bundler.js new file mode 100644 index 0000000..224e042 --- /dev/null +++ b/tools/lua-bundler.js @@ -0,0 +1,113 @@ +import fs from "fs"; +import path from "path"; + +/** + * @typedef Module + * @property {string} name + * @property {string} path + * @property {string|undefined} content + */ + +/** + * @param {Module[]} project + * @returns {[string, Module[]]} + */ +function createExecutableFromProject(project) { + const getModFnName = (name) => name.replace(/\./g, "_").replace(/^_/, ""); + /** @type {Module[]} */ + const contents = []; + + // filter out repeated modules with different import names + // and construct the executable Lua code + // (the main file content is handled separately) + for (let i = 0; i < project.length - 1; i++) { + const mod = project[i]; + + const existing = contents.find((m) => m.path === mod.path); + const moduleContent = + (!existing && + `-- module: "${mod.name}"\nlocal function _loaded_mod_${getModFnName(mod.name)}()\n${mod.content}\nend\n`) || + ""; + const requireMapper = `\n_G.package.loaded["${mod.name}"] = _loaded_mod_${getModFnName(existing?.name || mod.name)}()`; + + contents.push({ + ...mod, + content: moduleContent + requireMapper, + }); + } + + // finally, add the main file + contents.push(project[project.length - 1]); + + return [ + contents.reduce((acc, con) => acc + "\n\n" + con.content, ""), + contents, + ]; +} + +/** + * Create the project structure from the main file's content + * @param {string} mainFile + * @return {Module[]} + */ +function createProjectStructure(mainFile) { + const sorted = []; + const cwd = path.dirname(mainFile); + + // checks if the sorted module list already includes a node + const isSorted = (node) => + sorted.find((sortedNode) => sortedNode.path === node.path); + + // recursive dfs algorithm + function dfs(currentNode) { + const unvisitedChildNodes = exploreNodes(currentNode, cwd).filter( + (node) => !isSorted(node), + ); + + for (let i = 0; i < unvisitedChildNodes.length; i++) { + dfs(unvisitedChildNodes[i]); + } + + if (!isSorted(currentNode)) sorted.push(currentNode); + } + + // run DFS from the main file + dfs({ path: mainFile }); + + return sorted.filter( + // modules that were not read don't exist locally + // aos assumes that these modules have already been + // loaded into the process, or they're default modules + (mod) => mod.content !== undefined, + ); +} + +/** + * Find child nodes for a node (a module) + * @param {Module} node Parent node + * @param {string} cwd Project root dir + * @return {Module[]} + */ +function exploreNodes(node, cwd) { + if (!fs.existsSync(node.path)) return []; + + // set content + node.content = fs.readFileSync(node.path, "utf-8"); + + const requirePattern = /(?<=(require( *)(\n*)(\()?( *)("|'))).*(?=("|'))/g; + const requiredModules = + node.content.match(requirePattern)?.map((mod) => ({ + name: mod, + path: path.join(cwd, mod.replace(/\./g, "/") + ".lua"), + content: undefined, + })) || []; + + return requiredModules; +} + +export function bundle(entryLuaPath) { + const project = createProjectStructure(entryLuaPath); + const [bundledLua] = createExecutableFromProject(project); + + return bundledLua; +} diff --git a/tools/publish-aos.js b/tools/publish-aos.js new file mode 100644 index 0000000..c071c57 --- /dev/null +++ b/tools/publish-aos.js @@ -0,0 +1,34 @@ +import Arweave from "arweave"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const arweave = Arweave.init({ + host: "arweave.net", + port: 443, + protocol: "https", +}); +async function main() { + const bundledLua = fs.readFileSync( + path.join(__dirname, "../dist/aos-bundled.lua"), + "utf-8", + ); + const wallet = fs.readFileSync(path.join(__dirname, "key.json"), "utf-8"); + const jwk = JSON.parse(wallet); + const address = await arweave.wallets.jwkToAddress(jwk); + + console.log(`Publish AOS Lua with address ${address}`); + + const tx = await arweave.createTransaction({ data: bundledLua }, jwk); + tx.addTag("App-Name", "aos-LUA"); + tx.addTag("App-Version", "0.0.1"); + tx.addTag("Content-Type", "text/x-lua"); + tx.addTag("Author", "INSERT AUTHOR NAME"); + await arweave.transactions.sign(tx, jwk); + await arweave.transactions.post(tx); + + console.log("Transaction ID:", tx.id); +} +main(); diff --git a/tools/scripts/install-deps.js b/tools/scripts/install-deps.js new file mode 100644 index 0000000..d08b2b1 --- /dev/null +++ b/tools/scripts/install-deps.js @@ -0,0 +1,22 @@ +// initialize git submodules and install the ao dev cli +import { exec } from "child_process"; +import { promisify } from "util"; + +const execPromise = promisify(exec); + +async function installDeps() { + try { + // Initialize git submodules + await execPromise("git submodule update --init --recursive"); + console.log("Successfully initialized git submodules"); + + // Install the AO Dev CLI + await execPromise("curl -L https://install_ao.g8way.io | bash"); + console.log("Successfully installed the AO Dev CLI"); + } catch (err) { + console.error("Error installing dependencies:", err); + process.exit(1); + } +} + +installDeps(); diff --git a/tools/spawn-aos.js b/tools/spawn-aos.js new file mode 100644 index 0000000..73185aa --- /dev/null +++ b/tools/spawn-aos.js @@ -0,0 +1,53 @@ +import fs from "fs"; +import path from "path"; +import { createDataItemSigner, connect } from "@permaweb/aoconnect"; +import { fileURLToPath } from "url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const ao = connect({ + GATEWAY_URL: "https://arweave.net", +}); +const moduleId = "cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk"; +const scheduler = "_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA"; + +async function main() { + const luaCode = fs.readFileSync( + path.join(__dirname, "../dist/aos-bundled.lua"), + "utf-8", + ); + + const wallet = fs.readFileSync(path.join(__dirname, "key.json"), "utf-8"); + const signer = createDataItemSigner(JSON.parse(wallet)); + + const processId = await ao.spawn({ + module: moduleId, + scheduler, + signer, + }); + + console.log("Process ID:", processId); + console.log("Waiting 20 seconds to ensure process is readied."); + await new Promise((resolve) => setTimeout(resolve, 20_000)); + console.log("Loading ANT Lua code..."); + + const testCases = [["Eval", {}, luaCode]]; + + for (const [method, args, data] of testCases) { + const tags = args + ? Object.entries(args).map(([key, value]) => ({ name: key, value })) + : []; + const result = await ao + .message({ + process: processId, + tags: [...tags, { name: "Action", value: method }], + data, + signer, + }) + .catch((e) => e); + + console.dir({ method, result }, { depth: null }); + } +} + +main(); diff --git a/tools/utils.js b/tools/utils.js new file mode 100644 index 0000000..3d6a108 --- /dev/null +++ b/tools/utils.js @@ -0,0 +1,45 @@ +import AoLoader from "@permaweb/ao-loader"; +import { + AOS_WASM, + AO_LOADER_HANDLER_ENV, + AO_LOADER_OPTIONS, + BUNDLED_CHESS_GAME_AOS_LUA, + BUNDLED_CHESS_REGISTRY_AOS_LUA, + DEFAULT_HANDLE_OPTIONS, +} from "./constants.js"; + +/** + * Loads the aos wasm binary and returns the handle function with program memory + * @returns {Promise<{handle: Function, memory: WebAssembly.Memory}>} + */ + +export async function createAosLoader(lua) { + const handle = await AoLoader(AOS_WASM, AO_LOADER_OPTIONS); + + const evalRes = await handle( + null, + { + ...DEFAULT_HANDLE_OPTIONS, + Tags: [{ name: "Action", value: "Eval" }], + Data: lua, + }, + AO_LOADER_HANDLER_ENV, + ); + return { + handle, + memory: evalRes.Memory, + }; +} + +export async function createChessRegistryAosLoader() { + return createAosLoader(BUNDLED_CHESS_REGISTRY_AOS_LUA); +} + +export async function createChessGameAosLoader() { + return createAosLoader(BUNDLED_CHESS_GAME_AOS_LUA); +} + +export async function getHandlers(sendMessage, memory){ + + return sendMessage({Tags: [{name: "Action", value: "Eval"}], Data: "Handlers.list"}, memory) +} diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/.gitignore b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/.gitignore new file mode 100644 index 0000000..704a0a2 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/.gitignore @@ -0,0 +1,7 @@ +# lock files +package-lock.json +yarn.lock + +# binaries +process.js +process.wasm \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/README.md b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/README.md new file mode 100644 index 0000000..ca4ab5c --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/README.md @@ -0,0 +1,21 @@ +# aos + +This is the source code to the aos module, this module provides developers with the capability of designing and building process on the ao network in an interactive experience. When the design is complete the developer can transfer the ownership to a DAO process or brick the ownership so that the process can never be modified. + +## Build + +```sh +yarn build +``` + +## Testing + +```sh +yarn test +``` + +## Modules + +- [process](process.md) +- [handlers](handlers.md) +- [ao](ao.md) diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/ao.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/ao.lua new file mode 100644 index 0000000..faa6105 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/ao.lua @@ -0,0 +1,296 @@ +local oldao = ao or {} +local ao = { + _version = "0.0.6", + id = oldao.id or "", + _module = oldao._module or "", + authorities = oldao.authorities or {}, + reference = oldao.reference or 0, + outbox = oldao.outbox or + {Output = {}, Messages = {}, Spawns = {}, Assignments = {}}, + nonExtractableTags = { + 'Data-Protocol', 'Variant', 'From-Process', 'From-Module', 'Type', + 'From', 'Owner', 'Anchor', 'Target', 'Data', 'Tags' + }, + nonForwardableTags = { + 'Data-Protocol', 'Variant', 'From-Process', 'From-Module', 'Type', + 'From', 'Owner', 'Anchor', 'Target', 'Tags', 'TagArray', 'Hash-Chain', + 'Timestamp', 'Nonce', 'Epoch', 'Signature', 'Forwarded-By', + 'Pushed-For', 'Read-Only', 'Cron', 'Block-Height', 'Reference', 'Id', + 'Reply-To' + } +} + +local function _includes(list) + return function(key) + local exists = false + for _, listKey in ipairs(list) do + if key == listKey then + exists = true + break + end + end + if not exists then return false end + return true + end +end + +local function isArray(table) + if type(table) == "table" then + local maxIndex = 0 + for k, v in pairs(table) do + if type(k) ~= "number" or k < 1 or math.floor(k) ~= k then + return false -- If there's a non-integer key, it's not an array + end + maxIndex = math.max(maxIndex, k) + end + -- If the highest numeric index is equal to the number of elements, it's an array + return maxIndex == #table + end + return false +end + +local function padZero32(num) return string.format("%032d", num) end + +function ao.clone(obj, seen) + -- Handle non-tables and previously-seen tables. + if type(obj) ~= 'table' then return obj end + if seen and seen[obj] then return seen[obj] end + + -- New table; mark it as seen and copy recursively. + local s = seen or {} + local res = {} + s[obj] = res + for k, v in pairs(obj) do res[ao.clone(k, s)] = ao.clone(v, s) end + return setmetatable(res, getmetatable(obj)) +end + +function ao.normalize(msg) + for _, o in ipairs(msg.Tags) do + if not _includes(ao.nonExtractableTags)(o.name) then + msg[o.name] = o.value + end + end + return msg +end + +function ao.sanitize(msg) + local newMsg = ao.clone(msg) + + for k, _ in pairs(newMsg) do + if _includes(ao.nonForwardableTags)(k) then newMsg[k] = nil end + end + + return newMsg +end + +function ao.init(env) + if ao.id == "" then ao.id = env.Process.Id end + + if ao._module == "" then + for _, o in ipairs(env.Process.Tags) do + if o.name == "Module" then ao._module = o.value end + end + end + + if #ao.authorities < 1 then + for _, o in ipairs(env.Process.Tags) do + if o.name == "Authority" then + table.insert(ao.authorities, o.value) + end + end + end + + ao.outbox = {Output = {}, Messages = {}, Spawns = {}, Assignments = {}} + ao.env = env + +end + +function ao.log(txt) + if type(ao.outbox.Output) == 'string' then + ao.outbox.Output = {ao.outbox.Output} + end + table.insert(ao.outbox.Output, txt) +end + +-- clears outbox +function ao.clearOutbox() + ao.outbox = {Output = {}, Messages = {}, Spawns = {}, Assignments = {}} +end + +function ao.send(msg) + assert(type(msg) == 'table', 'msg should be a table') + ao.reference = ao.reference + 1 + local referenceString = tostring(ao.reference) + + local message = { + Target = msg.Target, + Data = msg.Data, + Anchor = padZero32(ao.reference), + Tags = { + {name = "Data-Protocol", value = "ao"}, + {name = "Variant", value = "ao.TN.1"}, + {name = "Type", value = "Message"}, + {name = "Reference", value = referenceString} + } + } + + -- if custom tags in root move them to tags + for k, v in pairs(msg) do + if not _includes({"Target", "Data", "Anchor", "Tags", "From"})(k) then + table.insert(message.Tags, {name = k, value = v}) + end + end + + if msg.Tags then + if isArray(msg.Tags) then + for _, o in ipairs(msg.Tags) do + table.insert(message.Tags, o) + end + else + for k, v in pairs(msg.Tags) do + table.insert(message.Tags, {name = k, value = v}) + end + end + end + + -- If running in an environment without the AOS Handlers module, do not add + -- the onReply and receive functions to the message. + if not Handlers then return message end + + -- clone message info and add to outbox + local extMessage = {} + for k, v in pairs(message) do extMessage[k] = v end + + -- add message to outbox + table.insert(ao.outbox.Messages, extMessage) + + -- add callback for onReply handler(s) + message.onReply = + function(...) -- Takes either (AddressThatWillReply, handler(s)) or (handler(s)) + local from, resolver + if select("#", ...) == 2 then + from = select(1, ...) + resolver = select(2, ...) + else + from = message.Target + resolver = select(1, ...) + end + + -- Add a one-time callback that runs the user's (matching) resolver on reply + Handlers.once({From = from, ["X-Reference"] = referenceString}, + resolver) + end + + message.receive = function(...) + local from = message.Target + if select("#", ...) == 1 then from = select(1, ...) end + return + Handlers.receive({From = from, ["X-Reference"] = referenceString}) + end + + return message +end + +function ao.spawn(module, msg) + assert(type(module) == "string", "Module source id is required!") + assert(type(msg) == 'table', 'Message must be a table') + -- inc spawn reference + ao.reference = ao.reference + 1 + local spawnRef = tostring(ao.reference) + + local spawn = { + Data = msg.Data or "NODATA", + Anchor = padZero32(ao.reference), + Tags = { + {name = "Data-Protocol", value = "ao"}, + {name = "Variant", value = "ao.TN.1"}, + {name = "Type", value = "Process"}, + {name = "From-Process", value = ao.id}, + {name = "From-Module", value = ao._module}, + {name = "Module", value = module}, + {name = "Reference", value = spawnRef} + } + } + + -- if custom tags in root move them to tags + for k, v in pairs(msg) do + if not _includes({"Target", "Data", "Anchor", "Tags", "From"})(k) then + table.insert(spawn.Tags, {name = k, value = v}) + end + end + + if msg.Tags then + if isArray(msg.Tags) then + for _, o in ipairs(msg.Tags) do + table.insert(spawn.Tags, o) + end + else + for k, v in pairs(msg.Tags) do + table.insert(spawn.Tags, {name = k, value = v}) + end + end + end + + -- If running in an environment without the AOS Handlers module, do not add + -- the after and receive functions to the spawn. + if not Handlers then return spawn end + + -- clone spawn info and add to outbox + local extSpawn = {} + for k, v in pairs(spawn) do extSpawn[k] = v end + + table.insert(ao.outbox.Spawns, extSpawn) + + -- add 'after' callback to returned table + -- local result = {} + spawn.onReply = function(callback) + Handlers.once({ + Action = "Spawned", + From = ao.id, + ["Reference"] = spawnRef + }, callback) + end + + spawn.receive = function() + return Handlers.receive({ + Action = "Spawned", + From = ao.id, + ["Reference"] = spawnRef + }) + + end + + return spawn +end + +function ao.assign(assignment) + assert(type(assignment) == 'table', 'assignment should be a table') + assert(type(assignment.Processes) == 'table', 'Processes should be a table') + assert(type(assignment.Message) == "string", "Message should be a string") + table.insert(ao.outbox.Assignments, assignment) +end + +-- The default security model of AOS processes: Trust all and *only* those +-- on the ao.authorities list. +function ao.isTrusted(msg) + for _, authority in ipairs(ao.authorities) do + if msg.From == authority then return true end + if msg.Owner == authority then return true end + end + return false +end + +function ao.result(result) + -- if error then only send the Error to CU + if ao.outbox.Error or result.Error then + return {Error = result.Error or ao.outbox.Error} + end + return { + Output = result.Output or ao.outbox.Output, + Messages = ao.outbox.Messages, + Spawns = ao.outbox.Spawns, + Assignments = ao.outbox.Assignments + } +end + +return ao diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/assignment.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/assignment.lua new file mode 100644 index 0000000..5ba30f0 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/assignment.lua @@ -0,0 +1,98 @@ + +local Assignment = { _version = "0.1.0" } +local utils = require('.utils') + +-- Implement assignable polyfills on _ao +function Assignment.init (ao) + local function findIndexByProp(array, prop, value) + for index, object in ipairs(array) do + if object[prop] == value then return index end + end + + return nil + end + + ao.assignables = ao.assignables or {} + + -- Add the MatchSpec to the ao.assignables table. A optional name may be provided. + -- This implies that ao.assignables may have both number and string indices. + -- + -- @tparam ?string|number|any nameOrMatchSpec The name of the MatchSpec + -- to be added to ao.assignables. if a MatchSpec is provided, then + -- no name is included + -- @tparam ?any matchSpec The MatchSpec to be added to ao.assignables. Only provided + -- if its name is passed as the first parameter + -- @treturn ?string|number name The name of the MatchSpec, either as provided + -- as an argument or as incremented + ao.addAssignable = ao.addAssignable or function (...) + local name = nil + local matchSpec = nil + + local idx = nil + + -- Initialize the parameters based on arguments + if select("#", ...) == 1 then + matchSpec = select(1, ...) + else + name = select(1, ...) + matchSpec = select(2, ...) + assert(type(name) == 'string', 'MatchSpec name MUST be a string') + end + + if name then idx = findIndexByProp(ao.assignables, "name", name) end + + if idx ~= nil and idx > 0 then + -- found update + ao.assignables[idx].pattern = matchSpec + else + -- append the new assignable, including potentially nil name + table.insert(ao.assignables, { pattern = matchSpec, name = name }) + end + end + + -- Remove the MatchSpec, either by name or by index + -- If the name is not found, or if the index does not exist, then do nothing. + -- + -- @tparam string|number name The name or index of the MatchSpec to be removed + -- @treturn nil nil + ao.removeAssignable = ao.removeAssignable or function (name) + local idx = nil + + if type(name) == 'string' then idx = findIndexByProp(ao.assignables, "name", name) + else + assert(type(name) == 'number', 'index MUST be a number') + idx = name + end + + if idx == nil or idx <= 0 or idx > #ao.assignables then return end + + table.remove(ao.assignables, idx) + end + + -- Return whether the msg is an assignment or not. This + -- can be determined by simply check whether the msg's Target is + -- This process' id + -- + -- @param msg The msg to be checked + -- @treturn boolean isAssignment + ao.isAssignment = ao.isAssignment or function (msg) return msg.Target ~= ao.id end + + -- Check whether the msg matches any assignable MatchSpec. + -- If not assignables are configured, the msg is deemed not assignable, by default. + -- + -- @param msg The msg to be checked + -- @treturn boolean isAssignable + ao.isAssignable = ao.isAssignable or function (msg) + for _, assignable in pairs(ao.assignables) do + if utils.matchesSpec(msg, assignable.pattern) then return true end + end + + -- If assignables is empty, the the above loop will noop, + -- and this expression will execute. + -- + -- In other words, all msgs are not assignable, by default. + return false + end +end + +return Assignment diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/base64.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/base64.lua new file mode 100644 index 0000000..6bf473f --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/base64.lua @@ -0,0 +1,201 @@ +--[[ + + base64 -- v1.5.3 public domain Lua base64 encoder/decoder + no warranty implied; use at your own risk + + Needs bit32.extract function. If not present it's implemented using BitOp + or Lua 5.3 native bit operators. For Lua 5.1 fallbacks to pure Lua + implementation inspired by Rici Lake's post: + http://ricilake.blogspot.co.uk/2007/10/iterating-bits-in-lua.html + + author: Ilya Kolbin (iskolbin@gmail.com) + url: github.com/iskolbin/lbase64 + + COMPATIBILITY + + Lua 5.1+, LuaJIT + + LICENSE + + See end of file for license information. + +--]] + + +local base64 = {} + +local extract = _G.bit32 and _G.bit32.extract -- Lua 5.2/Lua 5.3 in compatibility mode +if not extract then + if _G.bit then -- LuaJIT + local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band + extract = function( v, from, width ) + return band( shr( v, from ), shl( 1, width ) - 1 ) + end + elseif _G._VERSION == "Lua 5.1" then + extract = function( v, from, width ) + local w = 0 + local flag = 2^from + for i = 0, width-1 do + local flag2 = flag + flag + if v % flag2 >= flag then + w = w + 2^i + end + flag = flag2 + end + return w + end + else -- Lua 5.3+ + extract = load[[return function( v, from, width ) + return ( v >> from ) & ((1 << width) - 1) + end]]() + end +end + + +function base64.makeencoder( s62, s63, spad ) + local encoder = {} + for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J', + 'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y', + 'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n', + 'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2', + '3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do + encoder[b64code] = char:byte() + end + return encoder +end + +function base64.makedecoder( s62, s63, spad ) + local decoder = {} + for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do + decoder[charcode] = b64code + end + return decoder +end + +local DEFAULT_ENCODER = base64.makeencoder() +local DEFAULT_DECODER = base64.makedecoder() + +local char, concat = string.char, table.concat + +function base64.encode( str, encoder, usecaching ) + encoder = encoder or DEFAULT_ENCODER + local t, k, n = {}, 1, #str + local lastn = n % 3 + local cache = {} + for i = 1, n-lastn, 3 do + local a, b, c = str:byte( i, i+2 ) + local v = a*0x10000 + b*0x100 + c + local s + if usecaching then + s = cache[v] + if not s then + s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)]) + cache[v] = s + end + else + s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)]) + end + t[k] = s + k = k + 1 + end + if lastn == 2 then + local a, b = str:byte( n-1, n ) + local v = a*0x10000 + b*0x100 + t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64]) + elseif lastn == 1 then + local v = str:byte( n )*0x10000 + t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64]) + end + return concat( t ) +end + +function base64.decode( b64, decoder, usecaching ) + decoder = decoder or DEFAULT_DECODER + local pattern = '[^%w%+%/%=]' + if decoder then + local s62, s63 + for charcode, b64code in pairs( decoder ) do + if b64code == 62 then s62 = charcode + elseif b64code == 63 then s63 = charcode + end + end + pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) ) + end + b64 = b64:gsub( pattern, '' ) + local cache = usecaching and {} + local t, k = {}, 1 + local n = #b64 + local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0 + for i = 1, padding > 0 and n-4 or n, 4 do + local a, b, c, d = b64:byte( i, i+3 ) + local s + if usecaching then + local v0 = a*0x1000000 + b*0x10000 + c*0x100 + d + s = cache[v0] + if not s then + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d] + s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8)) + cache[v0] = s + end + else + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d] + s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8)) + end + t[k] = s + k = k + 1 + end + if padding == 1 then + local a, b, c = b64:byte( n-3, n-1 ) + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + t[k] = char( extract(v,16,8), extract(v,8,8)) + elseif padding == 2 then + local a, b = b64:byte( n-3, n-2 ) + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + t[k] = char( extract(v,16,8)) + end + return concat( t ) +end + +return base64 + +--[[ +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2018 Ilya Kolbin +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +--]] \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/bint.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/bint.lua new file mode 100644 index 0000000..ce2164a --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/bint.lua @@ -0,0 +1,1739 @@ +--[[-- +lua-bint - v0.5.1 - 26/Jun/2023 +Eduardo Bart - edub4rt@gmail.com +https://github.com/edubart/lua-bint + +Small portable arbitrary-precision integer arithmetic library in pure Lua for +computing with large integers. + +Different from most arbitrary-precision integer libraries in pure Lua out there this one +uses an array of lua integers as underlying data-type in its implementation instead of +using strings or large tables, this make it efficient for working with fixed width integers +and to make bitwise operations. + +## Design goals + +The main design goal of this library is to be small, correct, self contained and use few +resources while retaining acceptable performance and feature completeness. + +The library is designed to follow recent Lua integer semantics, this means that +integer overflow warps around, +signed integers are implemented using two-complement arithmetic rules, +integer division operations rounds towards minus infinity, +any mixed operations with float numbers promotes the value to a float, +and the usual division/power operation always promotes to floats. + +The library is designed to be possible to work with only unsigned integer arithmetic +when using the proper methods. + +All the lua arithmetic operators (+, -, *, //, /, %) and bitwise operators (&, |, ~, <<, >>) +are implemented as metamethods. + +The integer size must be fixed in advance and the library is designed to be more efficient when +working with integers of sizes between 64-4096 bits. If you need to work with really huge numbers +without size restrictions then use another library. This choice has been made to have more efficiency +in that specific size range. + +## Usage + +First on you should require the bint file including how many bits the bint module will work with, +by calling the returned function from the require, for example: + +```lua +local bint = require 'bint'(1024) +``` + +For more information about its arguments see @{newmodule}. +Then when you need create a bint, you can use one of the following functions: + +* @{bint.fromuinteger} (convert from lua integers, but read as unsigned integer) +* @{bint.frominteger} (convert from lua integers, preserving the sign) +* @{bint.frombase} (convert from arbitrary bases, like hexadecimal) +* @{bint.fromstring} (convert from arbitrary string, support binary/hexadecimal/decimal) +* @{bint.trunc} (convert from lua numbers, truncating the fractional part) +* @{bint.new} (convert from anything, asserts on invalid integers) +* @{bint.tobint} (convert from anything, returns nil on invalid integers) +* @{bint.parse} (convert from anything, returns a lua number as fallback) +* @{bint.zero} +* @{bint.one} +* `bint` + +You can also call `bint` as it is an alias to `bint.new`. +In doubt use @{bint.new} to create a new bint. + +Then you can use all the usual lua numeric operations on it, +all the arithmetic metamethods are implemented. +When you are done computing and need to get the result, +get the output from one of the following functions: + +* @{bint.touinteger} (convert to a lua integer, wraps around as an unsigned integer) +* @{bint.tointeger} (convert to a lua integer, wraps around, preserves the sign) +* @{bint.tonumber} (convert to lua float, losing precision) +* @{bint.tobase} (convert to a string in any base) +* @{bint.__tostring} (convert to a string in base 10) + +To output a very large integer with no loss you probably want to use @{bint.tobase} +or call `tostring` to get a string representation. + +## Precautions + +All library functions can be mixed with lua numbers, +this makes easy to mix operations between bints and lua numbers, +however the user should take care in some situations: + +* Don't mix integers and float operations if you want to work with integers only. +* Don't use the regular equal operator ('==') to compare values from this library, +unless you know in advance that both values are of the same primitive type, +otherwise it will always return false, use @{bint.eq} to be safe. +* Don't pass fractional numbers to functions that an integer is expected +* Don't mix operations between bint classes with different sizes as this is not supported, this +will throw assertions. +* Remember that casting back to lua integers or numbers precision can be lost. +* For dividing while preserving integers use the @{bint.__idiv} (the '//' operator). +* For doing power operation preserving integers use the @{bint.ipow} function. +* Configure the proper integer size you intend to work with, otherwise large integers may wrap around. + +]] + +-- Returns number of bits of the internal lua integer type. +local function luainteger_bitsize() + local n, i = -1, 0 + repeat + n, i = n >> 16, i + 16 + until n==0 + return i +end + +local math_type = math.type +local math_floor = math.floor +local math_abs = math.abs +local math_ceil = math.ceil +local math_modf = math.modf +local math_mininteger = math.mininteger +local math_maxinteger = math.maxinteger +local math_max = math.max +local math_min = math.min +local string_format = string.format +local table_insert = table.insert +local table_concat = table.concat +local table_unpack = table.unpack + +local memo = {} + +--- Create a new bint module representing integers of the desired bit size. +-- This is the returned function when `require 'bint'` is called. +-- @function newmodule +-- @param bits Number of bits for the integer representation, must be multiple of wordbits and +-- at least 64. +-- @param[opt] wordbits Number of the bits for the internal word, +-- defaults to half of Lua's integer size. +local function newmodule(bits, wordbits) + +local intbits = luainteger_bitsize() +bits = bits or 256 +wordbits = wordbits or (intbits // 2) + +-- Memoize bint modules +local memoindex = bits * 64 + wordbits +if memo[memoindex] then + return memo[memoindex] +end + +-- Validate +assert(bits % wordbits == 0, 'bitsize is not multiple of word bitsize') +assert(2*wordbits <= intbits, 'word bitsize must be half of the lua integer bitsize') +assert(bits >= 64, 'bitsize must be >= 64') +assert(wordbits >= 8, 'wordbits must be at least 8') +assert(bits % 8 == 0, 'bitsize must be multiple of 8') + +-- Create bint module +local bint = {} +bint.__index = bint + +--- Number of bits representing a bint instance. +bint.bits = bits + +-- Constants used internally +local BINT_BITS = bits +local BINT_BYTES = bits // 8 +local BINT_WORDBITS = wordbits +local BINT_SIZE = BINT_BITS // BINT_WORDBITS +local BINT_WORDMAX = (1 << BINT_WORDBITS) - 1 +local BINT_WORDMSB = (1 << (BINT_WORDBITS - 1)) +local BINT_LEPACKFMT = '<'..('I'..(wordbits // 8)):rep(BINT_SIZE) +local BINT_MATHMININTEGER, BINT_MATHMAXINTEGER +local BINT_MININTEGER + +--- Create a new bint with 0 value. +function bint.zero() + local x = setmetatable({}, bint) + for i=1,BINT_SIZE do + x[i] = 0 + end + return x +end +local bint_zero = bint.zero + +--- Create a new bint with 1 value. +function bint.one() + local x = setmetatable({}, bint) + x[1] = 1 + for i=2,BINT_SIZE do + x[i] = 0 + end + return x +end +local bint_one = bint.one + +-- Convert a value to a lua integer without losing precision. +local function tointeger(x) + x = tonumber(x) + local ty = math_type(x) + if ty == 'float' then + local floorx = math_floor(x) + if floorx == x then + x = floorx + ty = math_type(x) + end + end + if ty == 'integer' then + return x + end +end + +--- Create a bint from an unsigned integer. +-- Treats signed integers as an unsigned integer. +-- @param x A value to initialize from convertible to a lua integer. +-- @return A new bint or nil in case the input cannot be represented by an integer. +-- @see bint.frominteger +function bint.fromuinteger(x) + x = tointeger(x) + if x then + if x == 1 then + return bint_one() + elseif x == 0 then + return bint_zero() + end + local n = setmetatable({}, bint) + for i=1,BINT_SIZE do + n[i] = x & BINT_WORDMAX + x = x >> BINT_WORDBITS + end + return n + end +end +local bint_fromuinteger = bint.fromuinteger + +--- Create a bint from a signed integer. +-- @param x A value to initialize from convertible to a lua integer. +-- @return A new bint or nil in case the input cannot be represented by an integer. +-- @see bint.fromuinteger +function bint.frominteger(x) + x = tointeger(x) + if x then + if x == 1 then + return bint_one() + elseif x == 0 then + return bint_zero() + end + local neg = false + if x < 0 then + x = math_abs(x) + neg = true + end + local n = setmetatable({}, bint) + for i=1,BINT_SIZE do + n[i] = x & BINT_WORDMAX + x = x >> BINT_WORDBITS + end + if neg then + n:_unm() + end + return n + end +end +local bint_frominteger = bint.frominteger + +local basesteps = {} + +-- Compute the read step for frombase function +local function getbasestep(base) + local step = basesteps[base] + if step then + return step + end + step = 0 + local dmax = 1 + local limit = math_maxinteger // base + repeat + step = step + 1 + dmax = dmax * base + until dmax >= limit + basesteps[base] = step + return step +end + +-- Compute power with lua integers. +local function ipow(y, x, n) + if n == 1 then + return y * x + elseif n & 1 == 0 then --even + return ipow(y, x * x, n // 2) + end + return ipow(x * y, x * x, (n-1) // 2) +end + +--- Create a bint from a string of the desired base. +-- @param s The string to be converted from, +-- must have only alphanumeric and '+-' characters. +-- @param[opt] base Base that the number is represented, defaults to 10. +-- Must be at least 2 and at most 36. +-- @return A new bint or nil in case the conversion failed. +function bint.frombase(s, base) + if type(s) ~= 'string' then + return + end + base = base or 10 + if not (base >= 2 and base <= 36) then + -- number base is too large + return + end + local step = getbasestep(base) + if #s < step then + -- string is small, use tonumber (faster) + return bint_frominteger(tonumber(s, base)) + end + local sign, int = s:lower():match('^([+-]?)(%w+)$') + if not (sign and int) then + -- invalid integer string representation + return + end + local n = bint_zero() + for i=1,#int,step do + local part = int:sub(i,i+step-1) + local d = tonumber(part, base) + if not d then + -- invalid integer string representation + return + end + if i > 1 then + n = n * ipow(1, base, #part) + end + if d ~= 0 then + n:_add(d) + end + end + if sign == '-' then + n:_unm() + end + return n +end +local bint_frombase = bint.frombase + +--- Create a new bint from a string. +-- The string can by a decimal number, binary number prefixed with '0b' or hexadecimal number prefixed with '0x'. +-- @param s A string convertible to a bint. +-- @return A new bint or nil in case the conversion failed. +-- @see bint.frombase +function bint.fromstring(s) + if type(s) ~= 'string' then + return + end + if s:find('^[+-]?[0-9]+$') then + return bint_frombase(s, 10) + elseif s:find('^[+-]?0[xX][0-9a-fA-F]+$') then + return bint_frombase(s:gsub('0[xX]', '', 1), 16) + elseif s:find('^[+-]?0[bB][01]+$') then + return bint_frombase(s:gsub('0[bB]', '', 1), 2) + end +end +local bint_fromstring = bint.fromstring + +--- Create a new bint from a buffer of little-endian bytes. +-- @param buffer Buffer of bytes, extra bytes are trimmed from the right, missing bytes are padded to the right. +-- @raise An assert is thrown in case buffer is not an string. +-- @return A bint. +function bint.fromle(buffer) + assert(type(buffer) == 'string', 'buffer is not a string') + if #buffer > BINT_BYTES then -- trim extra bytes from the right + buffer = buffer:sub(1, BINT_BYTES) + elseif #buffer < BINT_BYTES then -- add missing bytes to the right + buffer = buffer..('\x00'):rep(BINT_BYTES - #buffer) + end + return setmetatable({BINT_LEPACKFMT:unpack(buffer)}, bint) +end + +--- Create a new bint from a buffer of big-endian bytes. +-- @param buffer Buffer of bytes, extra bytes are trimmed from the left, missing bytes are padded to the left. +-- @raise An assert is thrown in case buffer is not an string. +-- @return A bint. +function bint.frombe(buffer) + assert(type(buffer) == 'string', 'buffer is not a string') + if #buffer > BINT_BYTES then -- trim extra bytes from the left + buffer = buffer:sub(-BINT_BYTES, #buffer) + elseif #buffer < BINT_BYTES then -- add missing bytes to the left + buffer = ('\x00'):rep(BINT_BYTES - #buffer)..buffer + end + return setmetatable({BINT_LEPACKFMT:unpack(buffer:reverse())}, bint) +end + +--- Create a new bint from a value. +-- @param x A value convertible to a bint (string, number or another bint). +-- @return A new bint, guaranteed to be a new reference in case needed. +-- @raise An assert is thrown in case x is not convertible to a bint. +-- @see bint.tobint +-- @see bint.parse +function bint.new(x) + if getmetatable(x) ~= bint then + local ty = type(x) + if ty == 'number' then + x = bint_frominteger(x) + elseif ty == 'string' then + x = bint_fromstring(x) + end + assert(x, 'value cannot be represented by a bint') + return x + end + -- return a clone + local n = setmetatable({}, bint) + for i=1,BINT_SIZE do + n[i] = x[i] + end + return n +end +local bint_new = bint.new + +--- Convert a value to a bint if possible. +-- @param x A value to be converted (string, number or another bint). +-- @param[opt] clone A boolean that tells if a new bint reference should be returned. +-- Defaults to false. +-- @return A bint or nil in case the conversion failed. +-- @see bint.new +-- @see bint.parse +function bint.tobint(x, clone) + if getmetatable(x) == bint then + if not clone then + return x + end + -- return a clone + local n = setmetatable({}, bint) + for i=1,BINT_SIZE do + n[i] = x[i] + end + return n + end + local ty = type(x) + if ty == 'number' then + return bint_frominteger(x) + elseif ty == 'string' then + return bint_fromstring(x) + end +end +local tobint = bint.tobint + +--- Convert a value to a bint if possible otherwise to a lua number. +-- Useful to prepare values that you are unsure if it's going to be an integer or float. +-- @param x A value to be converted (string, number or another bint). +-- @param[opt] clone A boolean that tells if a new bint reference should be returned. +-- Defaults to false. +-- @return A bint or a lua number or nil in case the conversion failed. +-- @see bint.new +-- @see bint.tobint +function bint.parse(x, clone) + local i = tobint(x, clone) + if i then + return i + end + return tonumber(x) +end +local bint_parse = bint.parse + +--- Convert a bint to an unsigned integer. +-- Note that large unsigned integers may be represented as negatives in lua integers. +-- Note that lua cannot represent values larger than 64 bits, +-- in that case integer values wrap around. +-- @param x A bint or a number to be converted into an unsigned integer. +-- @return An integer or nil in case the input cannot be represented by an integer. +-- @see bint.tointeger +function bint.touinteger(x) + if getmetatable(x) == bint then + local n = 0 + for i=1,BINT_SIZE do + n = n | (x[i] << (BINT_WORDBITS * (i - 1))) + end + return n + end + return tointeger(x) +end + +--- Convert a bint to a signed integer. +-- It works by taking absolute values then applying the sign bit in case needed. +-- Note that lua cannot represent values larger than 64 bits, +-- in that case integer values wrap around. +-- @param x A bint or value to be converted into an unsigned integer. +-- @return An integer or nil in case the input cannot be represented by an integer. +-- @see bint.touinteger +function bint.tointeger(x) + if getmetatable(x) == bint then + local n = 0 + local neg = x:isneg() + if neg then + x = -x + end + for i=1,BINT_SIZE do + n = n | (x[i] << (BINT_WORDBITS * (i - 1))) + end + if neg then + n = -n + end + return n + end + return tointeger(x) +end +local bint_tointeger = bint.tointeger + +local function bint_assert_tointeger(x) + x = bint_tointeger(x) + if not x then + error('value has no integer representation') + end + return x +end + +--- Convert a bint to a lua float in case integer would wrap around or lua integer otherwise. +-- Different from @{bint.tointeger} the operation does not wrap around integers, +-- but digits precision are lost in the process of converting to a float. +-- @param x A bint or value to be converted into a lua number. +-- @return A lua number or nil in case the input cannot be represented by a number. +-- @see bint.tointeger +function bint.tonumber(x) + if getmetatable(x) == bint then + if x <= BINT_MATHMAXINTEGER and x >= BINT_MATHMININTEGER then + return x:tointeger() + end + return tonumber(tostring(x)) + end + return tonumber(x) +end +local bint_tonumber = bint.tonumber + +-- Compute base letters to use in bint.tobase +local BASE_LETTERS = {} +do + for i=1,36 do + BASE_LETTERS[i-1] = ('0123456789abcdefghijklmnopqrstuvwxyz'):sub(i,i) + end +end + +--- Convert a bint to a string in the desired base. +-- @param x The bint to be converted from. +-- @param[opt] base Base to be represented, defaults to 10. +-- Must be at least 2 and at most 36. +-- @param[opt] unsigned Whether to output as an unsigned integer. +-- Defaults to false for base 10 and true for others. +-- When unsigned is false the symbol '-' is prepended in negative values. +-- @return A string representing the input. +-- @raise An assert is thrown in case the base is invalid. +function bint.tobase(x, base, unsigned) + x = tobint(x) + if not x then + -- x is a fractional float or something else + return + end + base = base or 10 + if not (base >= 2 and base <= 36) then + -- number base is too large + return + end + if unsigned == nil then + unsigned = base ~= 10 + end + local isxneg = x:isneg() + if (base == 10 and not unsigned) or (base == 16 and unsigned and not isxneg) then + if x <= BINT_MATHMAXINTEGER and x >= BINT_MATHMININTEGER then + -- integer is small, use tostring or string.format (faster) + local n = x:tointeger() + if base == 10 then + return tostring(n) + elseif unsigned then + return string_format('%x', n) + end + end + end + local ss = {} + local neg = not unsigned and isxneg + x = neg and x:abs() or bint_new(x) + local xiszero = x:iszero() + if xiszero then + return '0' + end + -- calculate basepow + local step = 0 + local basepow = 1 + local limit = (BINT_WORDMSB - 1) // base + repeat + step = step + 1 + basepow = basepow * base + until basepow >= limit + -- serialize base digits + local size = BINT_SIZE + local xd, carry, d + repeat + -- single word division + carry = 0 + xiszero = true + for i=size,1,-1 do + carry = carry | x[i] + d, xd = carry // basepow, carry % basepow + if xiszero and d ~= 0 then + size = i + xiszero = false + end + x[i] = d + carry = xd << BINT_WORDBITS + end + -- digit division + for _=1,step do + xd, d = xd // base, xd % base + if xiszero and xd == 0 and d == 0 then + -- stop on leading zeros + break + end + table_insert(ss, 1, BASE_LETTERS[d]) + end + until xiszero + if neg then + table_insert(ss, 1, '-') + end + return table_concat(ss) +end + +local function bint_assert_convert(x) + return assert(tobint(x), 'value has not integer representation') +end + +--- Convert a bint to a buffer of little-endian bytes. +-- @param x A bint or lua integer. +-- @param[opt] trim If true, zero bytes on the right are trimmed. +-- @return A buffer of bytes representing the input. +-- @raise Asserts in case input is not convertible to an integer. +function bint.tole(x, trim) + x = bint_assert_convert(x) + local s = BINT_LEPACKFMT:pack(table_unpack(x)) + if trim then + s = s:gsub('\x00+$', '') + if s == '' then + s = '\x00' + end + end + return s +end + +--- Convert a bint to a buffer of big-endian bytes. +-- @param x A bint or lua integer. +-- @param[opt] trim If true, zero bytes on the left are trimmed. +-- @return A buffer of bytes representing the input. +-- @raise Asserts in case input is not convertible to an integer. +function bint.tobe(x, trim) + x = bint_assert_convert(x) + local s = BINT_LEPACKFMT:pack(table_unpack(x)):reverse() + if trim then + s = s:gsub('^\x00+', '') + if s == '' then + s = '\x00' + end + end + return s +end + +--- Check if a number is 0 considering bints. +-- @param x A bint or a lua number. +function bint.iszero(x) + if getmetatable(x) == bint then + for i=1,BINT_SIZE do + if x[i] ~= 0 then + return false + end + end + return true + end + return x == 0 +end + +--- Check if a number is 1 considering bints. +-- @param x A bint or a lua number. +function bint.isone(x) + if getmetatable(x) == bint then + if x[1] ~= 1 then + return false + end + for i=2,BINT_SIZE do + if x[i] ~= 0 then + return false + end + end + return true + end + return x == 1 +end + +--- Check if a number is -1 considering bints. +-- @param x A bint or a lua number. +function bint.isminusone(x) + if getmetatable(x) == bint then + for i=1,BINT_SIZE do + if x[i] ~= BINT_WORDMAX then + return false + end + end + return true + end + return x == -1 +end +local bint_isminusone = bint.isminusone + +--- Check if the input is a bint. +-- @param x Any lua value. +function bint.isbint(x) + return getmetatable(x) == bint +end + +--- Check if the input is a lua integer or a bint. +-- @param x Any lua value. +function bint.isintegral(x) + return getmetatable(x) == bint or math_type(x) == 'integer' +end + +--- Check if the input is a bint or a lua number. +-- @param x Any lua value. +function bint.isnumeric(x) + return getmetatable(x) == bint or type(x) == 'number' +end + +--- Get the number type of the input (bint, integer or float). +-- @param x Any lua value. +-- @return Returns "bint" for bints, "integer" for lua integers, +-- "float" from lua floats or nil otherwise. +function bint.type(x) + if getmetatable(x) == bint then + return 'bint' + end + return math_type(x) +end + +--- Check if a number is negative considering bints. +-- Zero is guaranteed to never be negative for bints. +-- @param x A bint or a lua number. +function bint.isneg(x) + if getmetatable(x) == bint then + return x[BINT_SIZE] & BINT_WORDMSB ~= 0 + end + return x < 0 +end +local bint_isneg = bint.isneg + +--- Check if a number is positive considering bints. +-- @param x A bint or a lua number. +function bint.ispos(x) + if getmetatable(x) == bint then + return not x:isneg() and not x:iszero() + end + return x > 0 +end + +--- Check if a number is even considering bints. +-- @param x A bint or a lua number. +function bint.iseven(x) + if getmetatable(x) == bint then + return x[1] & 1 == 0 + end + return math_abs(x) % 2 == 0 +end + +--- Check if a number is odd considering bints. +-- @param x A bint or a lua number. +function bint.isodd(x) + if getmetatable(x) == bint then + return x[1] & 1 == 1 + end + return math_abs(x) % 2 == 1 +end + +--- Create a new bint with the maximum possible integer value. +function bint.maxinteger() + local x = setmetatable({}, bint) + for i=1,BINT_SIZE-1 do + x[i] = BINT_WORDMAX + end + x[BINT_SIZE] = BINT_WORDMAX ~ BINT_WORDMSB + return x +end + +--- Create a new bint with the minimum possible integer value. +function bint.mininteger() + local x = setmetatable({}, bint) + for i=1,BINT_SIZE-1 do + x[i] = 0 + end + x[BINT_SIZE] = BINT_WORDMSB + return x +end + +--- Bitwise left shift a bint in one bit (in-place). +function bint:_shlone() + local wordbitsm1 = BINT_WORDBITS - 1 + for i=BINT_SIZE,2,-1 do + self[i] = ((self[i] << 1) | (self[i-1] >> wordbitsm1)) & BINT_WORDMAX + end + self[1] = (self[1] << 1) & BINT_WORDMAX + return self +end + +--- Bitwise right shift a bint in one bit (in-place). +function bint:_shrone() + local wordbitsm1 = BINT_WORDBITS - 1 + for i=1,BINT_SIZE-1 do + self[i] = ((self[i] >> 1) | (self[i+1] << wordbitsm1)) & BINT_WORDMAX + end + self[BINT_SIZE] = self[BINT_SIZE] >> 1 + return self +end + +-- Bitwise left shift words of a bint (in-place). Used only internally. +function bint:_shlwords(n) + for i=BINT_SIZE,n+1,-1 do + self[i] = self[i - n] + end + for i=1,n do + self[i] = 0 + end + return self +end + +-- Bitwise right shift words of a bint (in-place). Used only internally. +function bint:_shrwords(n) + if n < BINT_SIZE then + for i=1,BINT_SIZE-n do + self[i] = self[i + n] + end + for i=BINT_SIZE-n+1,BINT_SIZE do + self[i] = 0 + end + else + for i=1,BINT_SIZE do + self[i] = 0 + end + end + return self +end + +--- Increment a bint by one (in-place). +function bint:_inc() + for i=1,BINT_SIZE do + local tmp = self[i] + local v = (tmp + 1) & BINT_WORDMAX + self[i] = v + if v > tmp then + break + end + end + return self +end + +--- Increment a number by one considering bints. +-- @param x A bint or a lua number to increment. +function bint.inc(x) + local ix = tobint(x, true) + if ix then + return ix:_inc() + end + return x + 1 +end + +--- Decrement a bint by one (in-place). +function bint:_dec() + for i=1,BINT_SIZE do + local tmp = self[i] + local v = (tmp - 1) & BINT_WORDMAX + self[i] = v + if v <= tmp then + break + end + end + return self +end + +--- Decrement a number by one considering bints. +-- @param x A bint or a lua number to decrement. +function bint.dec(x) + local ix = tobint(x, true) + if ix then + return ix:_dec() + end + return x - 1 +end + +--- Assign a bint to a new value (in-place). +-- @param y A value to be copied from. +-- @raise Asserts in case inputs are not convertible to integers. +function bint:_assign(y) + y = bint_assert_convert(y) + for i=1,BINT_SIZE do + self[i] = y[i] + end + return self +end + +--- Take absolute of a bint (in-place). +function bint:_abs() + if self:isneg() then + self:_unm() + end + return self +end + +--- Take absolute of a number considering bints. +-- @param x A bint or a lua number to take the absolute. +function bint.abs(x) + local ix = tobint(x, true) + if ix then + return ix:_abs() + end + return math_abs(x) +end +local bint_abs = bint.abs + +--- Take the floor of a number considering bints. +-- @param x A bint or a lua number to perform the floor operation. +function bint.floor(x) + if getmetatable(x) == bint then + return bint_new(x) + end + return bint_new(math_floor(tonumber(x))) +end + +--- Take ceil of a number considering bints. +-- @param x A bint or a lua number to perform the ceil operation. +function bint.ceil(x) + if getmetatable(x) == bint then + return bint_new(x) + end + return bint_new(math_ceil(tonumber(x))) +end + +--- Wrap around bits of an integer (discarding left bits) considering bints. +-- @param x A bint or a lua integer. +-- @param y Number of right bits to preserve. +function bint.bwrap(x, y) + x = bint_assert_convert(x) + if y <= 0 then + return bint_zero() + elseif y < BINT_BITS then + return x & (bint_one() << y):_dec() + end + return bint_new(x) +end + +--- Rotate left integer x by y bits considering bints. +-- @param x A bint or a lua integer. +-- @param y Number of bits to rotate. +function bint.brol(x, y) + x, y = bint_assert_convert(x), bint_assert_tointeger(y) + if y > 0 then + return (x << y) | (x >> (BINT_BITS - y)) + elseif y < 0 then + if y ~= math_mininteger then + return x:bror(-y) + else + x:bror(-(y+1)) + x:bror(1) + end + end + return x +end + +--- Rotate right integer x by y bits considering bints. +-- @param x A bint or a lua integer. +-- @param y Number of bits to rotate. +function bint.bror(x, y) + x, y = bint_assert_convert(x), bint_assert_tointeger(y) + if y > 0 then + return (x >> y) | (x << (BINT_BITS - y)) + elseif y < 0 then + if y ~= math_mininteger then + return x:brol(-y) + else + x:brol(-(y+1)) + x:brol(1) + end + end + return x +end + +--- Truncate a number to a bint. +-- Floats numbers are truncated, that is, the fractional port is discarded. +-- @param x A number to truncate. +-- @return A new bint or nil in case the input does not fit in a bint or is not a number. +function bint.trunc(x) + if getmetatable(x) ~= bint then + x = tonumber(x) + if x then + local ty = math_type(x) + if ty == 'float' then + -- truncate to integer + x = math_modf(x) + end + return bint_frominteger(x) + end + return + end + return bint_new(x) +end + +--- Take maximum between two numbers considering bints. +-- @param x A bint or lua number to compare. +-- @param y A bint or lua number to compare. +-- @return A bint or a lua number. Guarantees to return a new bint for integer values. +function bint.max(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + return bint_new(ix > iy and ix or iy) + end + return bint_parse(math_max(x, y)) +end + +--- Take minimum between two numbers considering bints. +-- @param x A bint or lua number to compare. +-- @param y A bint or lua number to compare. +-- @return A bint or a lua number. Guarantees to return a new bint for integer values. +function bint.min(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + return bint_new(ix < iy and ix or iy) + end + return bint_parse(math_min(x, y)) +end + +--- Add an integer to a bint (in-place). +-- @param y An integer to be added. +-- @raise Asserts in case inputs are not convertible to integers. +function bint:_add(y) + y = bint_assert_convert(y) + local carry = 0 + for i=1,BINT_SIZE do + local tmp = self[i] + y[i] + carry + carry = tmp >> BINT_WORDBITS + self[i] = tmp & BINT_WORDMAX + end + return self +end + +--- Add two numbers considering bints. +-- @param x A bint or a lua number to be added. +-- @param y A bint or a lua number to be added. +function bint.__add(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + local z = setmetatable({}, bint) + local carry = 0 + for i=1,BINT_SIZE do + local tmp = ix[i] + iy[i] + carry + carry = tmp >> BINT_WORDBITS + z[i] = tmp & BINT_WORDMAX + end + return z + end + return bint_tonumber(x) + bint_tonumber(y) +end + +--- Subtract an integer from a bint (in-place). +-- @param y An integer to subtract. +-- @raise Asserts in case inputs are not convertible to integers. +function bint:_sub(y) + y = bint_assert_convert(y) + local borrow = 0 + local wordmaxp1 = BINT_WORDMAX + 1 + for i=1,BINT_SIZE do + local res = self[i] + wordmaxp1 - y[i] - borrow + self[i] = res & BINT_WORDMAX + borrow = (res >> BINT_WORDBITS) ~ 1 + end + return self +end + +--- Subtract two numbers considering bints. +-- @param x A bint or a lua number to be subtracted from. +-- @param y A bint or a lua number to subtract. +function bint.__sub(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + local z = setmetatable({}, bint) + local borrow = 0 + local wordmaxp1 = BINT_WORDMAX + 1 + for i=1,BINT_SIZE do + local res = ix[i] + wordmaxp1 - iy[i] - borrow + z[i] = res & BINT_WORDMAX + borrow = (res >> BINT_WORDBITS) ~ 1 + end + return z + end + return bint_tonumber(x) - bint_tonumber(y) +end + +--- Multiply two numbers considering bints. +-- @param x A bint or a lua number to multiply. +-- @param y A bint or a lua number to multiply. +function bint.__mul(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + local z = bint_zero() + local sizep1 = BINT_SIZE+1 + local s = sizep1 + local e = 0 + for i=1,BINT_SIZE do + if ix[i] ~= 0 or iy[i] ~= 0 then + e = math_max(e, i) + s = math_min(s, i) + end + end + for i=s,e do + for j=s,math_min(sizep1-i,e) do + local a = ix[i] * iy[j] + if a ~= 0 then + local carry = 0 + for k=i+j-1,BINT_SIZE do + local tmp = z[k] + (a & BINT_WORDMAX) + carry + carry = tmp >> BINT_WORDBITS + z[k] = tmp & BINT_WORDMAX + a = a >> BINT_WORDBITS + end + end + end + end + return z + end + return bint_tonumber(x) * bint_tonumber(y) +end + +--- Check if bints are equal. +-- @param x A bint to compare. +-- @param y A bint to compare. +function bint.__eq(x, y) + for i=1,BINT_SIZE do + if x[i] ~= y[i] then + return false + end + end + return true +end + +--- Check if numbers are equal considering bints. +-- @param x A bint or lua number to compare. +-- @param y A bint or lua number to compare. +function bint.eq(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + return ix == iy + end + return x == y +end +local bint_eq = bint.eq + +local function findleftbit(x) + for i=BINT_SIZE,1,-1 do + local v = x[i] + if v ~= 0 then + local j = 0 + repeat + v = v >> 1 + j = j + 1 + until v == 0 + return (i-1)*BINT_WORDBITS + j - 1, i + end + end +end + +-- Single word division modulus +local function sudivmod(nume, deno) + local rema + local carry = 0 + for i=BINT_SIZE,1,-1 do + carry = carry | nume[i] + nume[i] = carry // deno + rema = carry % deno + carry = rema << BINT_WORDBITS + end + return rema +end + +--- Perform unsigned division and modulo operation between two integers considering bints. +-- This is effectively the same of @{bint.udiv} and @{bint.umod}. +-- @param x The numerator, must be a bint or a lua integer. +-- @param y The denominator, must be a bint or a lua integer. +-- @return The quotient following the remainder, both bints. +-- @raise Asserts on attempt to divide by zero +-- or if inputs are not convertible to integers. +-- @see bint.udiv +-- @see bint.umod +function bint.udivmod(x, y) + local nume = bint_new(x) + local deno = bint_assert_convert(y) + -- compute if high bits of denominator are all zeros + local ishighzero = true + for i=2,BINT_SIZE do + if deno[i] ~= 0 then + ishighzero = false + break + end + end + if ishighzero then + -- try to divide by a single word (optimization) + local low = deno[1] + assert(low ~= 0, 'attempt to divide by zero') + if low == 1 then + -- denominator is one + return nume, bint_zero() + elseif low <= (BINT_WORDMSB - 1) then + -- can do single word division + local rema = sudivmod(nume, low) + return nume, bint_fromuinteger(rema) + end + end + if nume:ult(deno) then + -- denominator is greater than numerator + return bint_zero(), nume + end + -- align leftmost digits in numerator and denominator + local denolbit = findleftbit(deno) + local numelbit, numesize = findleftbit(nume) + local bit = numelbit - denolbit + deno = deno << bit + local wordmaxp1 = BINT_WORDMAX + 1 + local wordbitsm1 = BINT_WORDBITS - 1 + local denosize = numesize + local quot = bint_zero() + while bit >= 0 do + -- compute denominator <= numerator + local le = true + local size = math_max(numesize, denosize) + for i=size,1,-1 do + local a, b = deno[i], nume[i] + if a ~= b then + le = a < b + break + end + end + -- if the portion of the numerator above the denominator is greater or equal than to the denominator + if le then + -- subtract denominator from the portion of the numerator + local borrow = 0 + for i=1,size do + local res = nume[i] + wordmaxp1 - deno[i] - borrow + nume[i] = res & BINT_WORDMAX + borrow = (res >> BINT_WORDBITS) ~ 1 + end + -- concatenate 1 to the right bit of the quotient + local i = (bit // BINT_WORDBITS) + 1 + quot[i] = quot[i] | (1 << (bit % BINT_WORDBITS)) + end + -- shift right the denominator in one bit + for i=1,denosize-1 do + deno[i] = ((deno[i] >> 1) | (deno[i+1] << wordbitsm1)) & BINT_WORDMAX + end + local lastdenoword = deno[denosize] >> 1 + deno[denosize] = lastdenoword + -- recalculate denominator size (optimization) + if lastdenoword == 0 then + while deno[denosize] == 0 do + denosize = denosize - 1 + end + if denosize == 0 then + break + end + end + -- decrement current set bit for the quotient + bit = bit - 1 + end + -- the remaining numerator is the remainder + return quot, nume +end +local bint_udivmod = bint.udivmod + +--- Perform unsigned division between two integers considering bints. +-- @param x The numerator, must be a bint or a lua integer. +-- @param y The denominator, must be a bint or a lua integer. +-- @return The quotient, a bint. +-- @raise Asserts on attempt to divide by zero +-- or if inputs are not convertible to integers. +function bint.udiv(x, y) + return (bint_udivmod(x, y)) +end + +--- Perform unsigned integer modulo operation between two integers considering bints. +-- @param x The numerator, must be a bint or a lua integer. +-- @param y The denominator, must be a bint or a lua integer. +-- @return The remainder, a bint. +-- @raise Asserts on attempt to divide by zero +-- or if the inputs are not convertible to integers. +function bint.umod(x, y) + local _, rema = bint_udivmod(x, y) + return rema +end +local bint_umod = bint.umod + +--- Perform integer truncate division and modulo operation between two numbers considering bints. +-- This is effectively the same of @{bint.tdiv} and @{bint.tmod}. +-- @param x The numerator, a bint or lua number. +-- @param y The denominator, a bint or lua number. +-- @return The quotient following the remainder, both bint or lua number. +-- @raise Asserts on attempt to divide by zero or on division overflow. +-- @see bint.tdiv +-- @see bint.tmod +function bint.tdivmod(x, y) + local ax, ay = bint_abs(x), bint_abs(y) + local ix, iy = tobint(ax), tobint(ay) + local quot, rema + if ix and iy then + assert(not (bint_eq(x, BINT_MININTEGER) and bint_isminusone(y)), 'division overflow') + quot, rema = bint_udivmod(ix, iy) + else + quot, rema = ax // ay, ax % ay + end + local isxneg, isyneg = bint_isneg(x), bint_isneg(y) + if isxneg ~= isyneg then + quot = -quot + end + if isxneg then + rema = -rema + end + return quot, rema +end +local bint_tdivmod = bint.tdivmod + +--- Perform truncate division between two numbers considering bints. +-- Truncate division is a division that rounds the quotient towards zero. +-- @param x The numerator, a bint or lua number. +-- @param y The denominator, a bint or lua number. +-- @return The quotient, a bint or lua number. +-- @raise Asserts on attempt to divide by zero or on division overflow. +function bint.tdiv(x, y) + return (bint_tdivmod(x, y)) +end + +--- Perform integer truncate modulo operation between two numbers considering bints. +-- The operation is defined as the remainder of the truncate division +-- (division that rounds the quotient towards zero). +-- @param x The numerator, a bint or lua number. +-- @param y The denominator, a bint or lua number. +-- @return The remainder, a bint or lua number. +-- @raise Asserts on attempt to divide by zero or on division overflow. +function bint.tmod(x, y) + local _, rema = bint_tdivmod(x, y) + return rema +end + +--- Perform integer floor division and modulo operation between two numbers considering bints. +-- This is effectively the same of @{bint.__idiv} and @{bint.__mod}. +-- @param x The numerator, a bint or lua number. +-- @param y The denominator, a bint or lua number. +-- @return The quotient following the remainder, both bint or lua number. +-- @raise Asserts on attempt to divide by zero. +-- @see bint.__idiv +-- @see bint.__mod +function bint.idivmod(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + local isnumeneg = ix[BINT_SIZE] & BINT_WORDMSB ~= 0 + local isdenoneg = iy[BINT_SIZE] & BINT_WORDMSB ~= 0 + if isnumeneg then + ix = -ix + end + if isdenoneg then + iy = -iy + end + local quot, rema = bint_udivmod(ix, iy) + if isnumeneg ~= isdenoneg then + quot:_unm() + -- round quotient towards minus infinity + if not rema:iszero() then + quot:_dec() + -- adjust the remainder + if isnumeneg and not isdenoneg then + rema:_unm():_add(y) + elseif isdenoneg and not isnumeneg then + rema:_add(y) + end + end + elseif isnumeneg then + -- adjust the remainder + rema:_unm() + end + return quot, rema + end + local nx, ny = bint_tonumber(x), bint_tonumber(y) + return nx // ny, nx % ny +end +local bint_idivmod = bint.idivmod + +--- Perform floor division between two numbers considering bints. +-- Floor division is a division that rounds the quotient towards minus infinity, +-- resulting in the floor of the division of its operands. +-- @param x The numerator, a bint or lua number. +-- @param y The denominator, a bint or lua number. +-- @return The quotient, a bint or lua number. +-- @raise Asserts on attempt to divide by zero. +function bint.__idiv(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + local isnumeneg = ix[BINT_SIZE] & BINT_WORDMSB ~= 0 + local isdenoneg = iy[BINT_SIZE] & BINT_WORDMSB ~= 0 + if isnumeneg then + ix = -ix + end + if isdenoneg then + iy = -iy + end + local quot, rema = bint_udivmod(ix, iy) + if isnumeneg ~= isdenoneg then + quot:_unm() + -- round quotient towards minus infinity + if not rema:iszero() then + quot:_dec() + end + end + return quot, rema + end + return bint_tonumber(x) // bint_tonumber(y) +end + +--- Perform division between two numbers considering bints. +-- This always casts inputs to floats, for integer division only use @{bint.__idiv}. +-- @param x The numerator, a bint or lua number. +-- @param y The denominator, a bint or lua number. +-- @return The quotient, a lua number. +function bint.__div(x, y) + return bint_tonumber(x) / bint_tonumber(y) +end + +--- Perform integer floor modulo operation between two numbers considering bints. +-- The operation is defined as the remainder of the floor division +-- (division that rounds the quotient towards minus infinity). +-- @param x The numerator, a bint or lua number. +-- @param y The denominator, a bint or lua number. +-- @return The remainder, a bint or lua number. +-- @raise Asserts on attempt to divide by zero. +function bint.__mod(x, y) + local _, rema = bint_idivmod(x, y) + return rema +end + +--- Perform integer power between two integers considering bints. +-- If y is negative then pow is performed as an unsigned integer. +-- @param x The base, an integer. +-- @param y The exponent, an integer. +-- @return The result of the pow operation, a bint. +-- @raise Asserts in case inputs are not convertible to integers. +-- @see bint.__pow +-- @see bint.upowmod +function bint.ipow(x, y) + y = bint_assert_convert(y) + if y:iszero() then + return bint_one() + elseif y:isone() then + return bint_new(x) + end + -- compute exponentiation by squaring + x, y = bint_new(x), bint_new(y) + local z = bint_one() + repeat + if y:iseven() then + x = x * x + y:_shrone() + else + z = x * z + x = x * x + y:_dec():_shrone() + end + until y:isone() + return x * z +end + +--- Perform integer power between two unsigned integers over a modulus considering bints. +-- @param x The base, an integer. +-- @param y The exponent, an integer. +-- @param m The modulus, an integer. +-- @return The result of the pow operation, a bint. +-- @raise Asserts in case inputs are not convertible to integers. +-- @see bint.__pow +-- @see bint.ipow +function bint.upowmod(x, y, m) + m = bint_assert_convert(m) + if m:isone() then + return bint_zero() + end + x, y = bint_new(x), bint_new(y) + local z = bint_one() + x = bint_umod(x, m) + while not y:iszero() do + if y:isodd() then + z = bint_umod(z*x, m) + end + y:_shrone() + x = bint_umod(x*x, m) + end + return z +end + +--- Perform numeric power between two numbers considering bints. +-- This always casts inputs to floats, for integer power only use @{bint.ipow}. +-- @param x The base, a bint or lua number. +-- @param y The exponent, a bint or lua number. +-- @return The result of the pow operation, a lua number. +-- @see bint.ipow +function bint.__pow(x, y) + return bint_tonumber(x) ^ bint_tonumber(y) +end + +--- Bitwise left shift integers considering bints. +-- @param x An integer to perform the bitwise shift. +-- @param y An integer with the number of bits to shift. +-- @return The result of shift operation, a bint. +-- @raise Asserts in case inputs are not convertible to integers. +function bint.__shl(x, y) + x, y = bint_new(x), bint_assert_tointeger(y) + if y == math_mininteger or math_abs(y) >= BINT_BITS then + return bint_zero() + end + if y < 0 then + return x >> -y + end + local nvals = y // BINT_WORDBITS + if nvals ~= 0 then + x:_shlwords(nvals) + y = y - nvals * BINT_WORDBITS + end + if y ~= 0 then + local wordbitsmy = BINT_WORDBITS - y + for i=BINT_SIZE,2,-1 do + x[i] = ((x[i] << y) | (x[i-1] >> wordbitsmy)) & BINT_WORDMAX + end + x[1] = (x[1] << y) & BINT_WORDMAX + end + return x +end + +--- Bitwise right shift integers considering bints. +-- @param x An integer to perform the bitwise shift. +-- @param y An integer with the number of bits to shift. +-- @return The result of shift operation, a bint. +-- @raise Asserts in case inputs are not convertible to integers. +function bint.__shr(x, y) + x, y = bint_new(x), bint_assert_tointeger(y) + if y == math_mininteger or math_abs(y) >= BINT_BITS then + return bint_zero() + end + if y < 0 then + return x << -y + end + local nvals = y // BINT_WORDBITS + if nvals ~= 0 then + x:_shrwords(nvals) + y = y - nvals * BINT_WORDBITS + end + if y ~= 0 then + local wordbitsmy = BINT_WORDBITS - y + for i=1,BINT_SIZE-1 do + x[i] = ((x[i] >> y) | (x[i+1] << wordbitsmy)) & BINT_WORDMAX + end + x[BINT_SIZE] = x[BINT_SIZE] >> y + end + return x +end + +--- Bitwise AND bints (in-place). +-- @param y An integer to perform bitwise AND. +-- @raise Asserts in case inputs are not convertible to integers. +function bint:_band(y) + y = bint_assert_convert(y) + for i=1,BINT_SIZE do + self[i] = self[i] & y[i] + end + return self +end + +--- Bitwise AND two integers considering bints. +-- @param x An integer to perform bitwise AND. +-- @param y An integer to perform bitwise AND. +-- @raise Asserts in case inputs are not convertible to integers. +function bint.__band(x, y) + return bint_new(x):_band(y) +end + +--- Bitwise OR bints (in-place). +-- @param y An integer to perform bitwise OR. +-- @raise Asserts in case inputs are not convertible to integers. +function bint:_bor(y) + y = bint_assert_convert(y) + for i=1,BINT_SIZE do + self[i] = self[i] | y[i] + end + return self +end + +--- Bitwise OR two integers considering bints. +-- @param x An integer to perform bitwise OR. +-- @param y An integer to perform bitwise OR. +-- @raise Asserts in case inputs are not convertible to integers. +function bint.__bor(x, y) + return bint_new(x):_bor(y) +end + +--- Bitwise XOR bints (in-place). +-- @param y An integer to perform bitwise XOR. +-- @raise Asserts in case inputs are not convertible to integers. +function bint:_bxor(y) + y = bint_assert_convert(y) + for i=1,BINT_SIZE do + self[i] = self[i] ~ y[i] + end + return self +end + +--- Bitwise XOR two integers considering bints. +-- @param x An integer to perform bitwise XOR. +-- @param y An integer to perform bitwise XOR. +-- @raise Asserts in case inputs are not convertible to integers. +function bint.__bxor(x, y) + return bint_new(x):_bxor(y) +end + +--- Bitwise NOT a bint (in-place). +function bint:_bnot() + for i=1,BINT_SIZE do + self[i] = (~self[i]) & BINT_WORDMAX + end + return self +end + +--- Bitwise NOT a bint. +-- @param x An integer to perform bitwise NOT. +-- @raise Asserts in case inputs are not convertible to integers. +function bint.__bnot(x) + local y = setmetatable({}, bint) + for i=1,BINT_SIZE do + y[i] = (~x[i]) & BINT_WORDMAX + end + return y +end + +--- Negate a bint (in-place). This effectively applies two's complements. +function bint:_unm() + return self:_bnot():_inc() +end + +--- Negate a bint. This effectively applies two's complements. +-- @param x A bint to perform negation. +function bint.__unm(x) + return (~x):_inc() +end + +--- Compare if integer x is less than y considering bints (unsigned version). +-- @param x Left integer to compare. +-- @param y Right integer to compare. +-- @raise Asserts in case inputs are not convertible to integers. +-- @see bint.__lt +function bint.ult(x, y) + x, y = bint_assert_convert(x), bint_assert_convert(y) + for i=BINT_SIZE,1,-1 do + local a, b = x[i], y[i] + if a ~= b then + return a < b + end + end + return false +end + +--- Compare if bint x is less or equal than y considering bints (unsigned version). +-- @param x Left integer to compare. +-- @param y Right integer to compare. +-- @raise Asserts in case inputs are not convertible to integers. +-- @see bint.__le +function bint.ule(x, y) + x, y = bint_assert_convert(x), bint_assert_convert(y) + for i=BINT_SIZE,1,-1 do + local a, b = x[i], y[i] + if a ~= b then + return a < b + end + end + return true +end + +--- Compare if number x is less than y considering bints and signs. +-- @param x Left value to compare, a bint or lua number. +-- @param y Right value to compare, a bint or lua number. +-- @see bint.ult +function bint.__lt(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + local xneg = ix[BINT_SIZE] & BINT_WORDMSB ~= 0 + local yneg = iy[BINT_SIZE] & BINT_WORDMSB ~= 0 + if xneg == yneg then + for i=BINT_SIZE,1,-1 do + local a, b = ix[i], iy[i] + if a ~= b then + return a < b + end + end + return false + end + return xneg and not yneg + end + return bint_tonumber(x) < bint_tonumber(y) +end + +--- Compare if number x is less or equal than y considering bints and signs. +-- @param x Left value to compare, a bint or lua number. +-- @param y Right value to compare, a bint or lua number. +-- @see bint.ule +function bint.__le(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + local xneg = ix[BINT_SIZE] & BINT_WORDMSB ~= 0 + local yneg = iy[BINT_SIZE] & BINT_WORDMSB ~= 0 + if xneg == yneg then + for i=BINT_SIZE,1,-1 do + local a, b = ix[i], iy[i] + if a ~= b then + return a < b + end + end + return true + end + return xneg and not yneg + end + return bint_tonumber(x) <= bint_tonumber(y) +end + +--- Convert a bint to a string on base 10. +-- @see bint.tobase +function bint:__tostring() + return self:tobase(10) +end + +-- Allow creating bints by calling bint itself +setmetatable(bint, { + __call = function(_, x) + return bint_new(x) + end +}) + +BINT_MATHMININTEGER, BINT_MATHMAXINTEGER = bint_new(math.mininteger), bint_new(math.maxinteger) +BINT_MININTEGER = bint.mininteger() +memo[memoindex] = bint + +return bint + +end + +return newmodule \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/chance.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/chance.lua new file mode 100644 index 0000000..c6f576a --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/chance.lua @@ -0,0 +1,86 @@ +local N = 624 +local M = 397 +local MATRIX_A = 0x9908b0df +local UPPER_MASK = 0x80000000 +local LOWER_MASK = 0x7fffffff + +-- initializes mt[N] with a seed +local function init_genrand(o, s) + o.mt[0] = s & 0xffffffff + for i = 1, N - 1 do + o.mt[i] = (1812433253 * (o.mt[i - 1] ~ (o.mt[i - 1] >> 30))) + i + -- See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. + -- In the previous versions, MSBs of the seed affect + -- only MSBs of the array mt[]. + -- 2002/01/09 modified by Makoto Matsumoto + o.mt[i] = o.mt[i] & 0xffffffff + -- for >32 bit machines + end + o.mti = N +end + +-- generates a random number on [0,0xffffffff]-interval +local function genrand_int32(o) + local y + local mag01 = {} -- mag01[x] = x * MATRIX_A for x=0,1 + mag01[0] = 0x0 + mag01[1] = MATRIX_A + if o.mti >= N then -- generate N words at one time + if o.mti == N + 1 then -- if init_genrand() has not been called, + init_genrand(o, 5489) -- a default initial seed is used + end + for kk = 0, N - M - 1 do + y = (o.mt[kk] & UPPER_MASK) | (o.mt[kk + 1] & LOWER_MASK) + o.mt[kk] = o.mt[kk + M] ~ (y >> 1) ~ mag01[y & 0x1] + end + for kk = N - M, N - 2 do + y = (o.mt[kk] & UPPER_MASK) | (o.mt[kk + 1] & LOWER_MASK) + o.mt[kk] = o.mt[kk + (M - N)] ~ (y >> 1) ~ mag01[y & 0x1] + end + y = (o.mt[N - 1] & UPPER_MASK) | (o.mt[0] & LOWER_MASK) + o.mt[N - 1] = o.mt[M - 1] ~ (y >> 1) ~ mag01[y & 0x1] + + o.mti = 0 + end + + y = o.mt[o.mti] + o.mti = o.mti + 1 + + -- Tempering + y = y ~ (y >> 11) + y = y ~ ((y << 7) & 0x9d2c5680) + y = y ~ ((y << 15) & 0xefc60000) + y = y ~ (y >> 18) + + return y +end + +local MersenneTwister = {} +MersenneTwister.mt = {} +MersenneTwister.mti = N + 1 + + +local Random = {} + +-- set new random seed +function Random.seed(seed) + init_genrand(MersenneTwister, seed) +end + +-- generates a random number on [0,1)-real-interval +function Random.random() + return genrand_int32(MersenneTwister) * (1.0 / 4294967296.0) +end + +--[[ +return a random integer +NOTE the min and max are INCLUDED in the range. +the max integer in lua is math.maxinteger +the min is math.mininteger +]] +function Random.integer(min, max) + assert(max >= min, "max must bigger than min") + return math.floor(Random.random() * (max - min + 1) + min) +end + +return Random \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto.md b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto.md new file mode 100644 index 0000000..aa38864 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto.md @@ -0,0 +1,852 @@ +# README for Lua Module: `crypto` (v0.0.1) + +## Overview + +The `crypto` module provides a set of cryptographic primitives like digests, ciphers and other cryptographic algorithms in pure Lua. It offers several functionalities to hash, encrypt and decrypt data, simplifying the development of secure communication and data storage. This document will guide you through the module's functionalities, installation, and usage. + +### Version + +0.0.1 + +## Installation + +1. Ensure you have Lua installed on your AOS computer system. +2. Copy the `crypto` folder to your project directory or a designated Lua libraries directory. +3. Include the module in your Lua scripts using `local crypto = require('.crypto')`. + +## Primitives + +1. Digests (sha1, sha2, sha3, keccak, blake2b, etc.) +2. Ciphers (AES, ISSAC, Morus, NORX, etc.) +3. Random Number Generators (ISAAC) +4. MACs (HMAC) +5. KDFs (PBKDF2) +6. Utilities (Array, Stream, Queue, etc.) + +--- + +# Digests + +## MD2 + +Calculates the MD2 digest of a given message. + +- **Parameters:** + + - `stream` (Stream): The message in form of stream + +- **Returns:** A table containing functions to get digest in different formats. + - `asBytes()`: The digest as byte table. + - `asHex()`: The digest as string in hexadecimal format. + - `asString()`: The digest as string format. + +Example: + +```lua +local str = crypto.utils.stream.fromString("ao") + +return crypto.digest.md2(str).asHex() -- 0d4e80edd07bee6c7965b21b25a9b1ea +``` + +## MD4 + +Calculates the MD4 digest of a given message. + +- **Parameters:** + + - `stream` (Stream): The message in form of stream + +- **Returns:** A table containing functions to get digest in different formats. + - `asBytes()`: The digest as byte table. + - `asHex()`: The digest as string in hexadecimal format. + - `asString()`: The digest as string format. + +Example: + +```lua +local str = crypto.utils.stream.fromString("ao") + +return crypto.digest.md4(str).asHex() -- e068dfe3d8cb95311b58be566db66954 +``` + +## MD5 + +Calculates the MD5 digest of a given message. + +- **Parameters:** + + - `stream` (Stream): The message in form of stream + +- **Returns:** A table containing functions to get digest in different formats. + - `asBytes()`: The digest as byte table. + - `asHex()`: The digest as string in hexadecimal format. + - `asString()`: The digest as string format. + +Example: + +```lua +local str = crypto.utils.stream.fromString("ao") + +return crypto.digest.md5(str).asHex() -- adac5e63f80f8629e9573527b25891d3 +``` + +## SHA1 + +Calculates the SHA1 digest of a given message. + +- **Parameters:** + + - `stream` (Stream): The message in form of stream + +- **Returns:** A table containing functions to get digest in different formats. + - `asBytes()`: The digest as byte table. + - `asHex()`: The digest as string in hexadecimal format. + - `asString()`: The digest as string format. + +Example: + +```lua +local str = crypto.utils.stream.fromString("ao") + +return crypto.digest.sha1(str).asHex() -- c29dd6c83b67a1d6d3b28588a1f068b68689aa1d +``` + +## SHA2_256 + +Calculates the SHA2-256 digest of a given message. + +- **Parameters:** + - `stream` (Stream): The message in form of stream +- **Returns:** A table containing functions to get digest in different formats. + - `asBytes()`: The digest as byte table. + - `asHex()`: The digest as string in hexadecimal format. + - `asString()`: The digest as string format. + +Example: + +```lua +local str = crypto.utils.stream.fromString("ao") + +return crypto.digest.sha2_256(str).asHex() -- ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad +``` + +## SHA2_512 + +Calculates the SHA2-512 digest of a given message. + +- **Parameters:** + - `msg` (string): The message to calculate the digest +- **Returns:** A table containing functions to get digest in different formats. + - `asBytes()`: The digest as byte table. + - `asHex()`: The digest as string in hexadecimal format. + - `asString()`: The digest as string format. + +Example: + +```lua +local str = "ao" + +return crypto.digest.sha2_512(str).asHex() -- 6f36a696b17ce5a71efa700e8a7e47994f3e134a5e5f387b3e7c2c912abe94f94ee823f9b9dcae59af99e2e34c8b4fb0bd592260c6720ee49e5deaac2065c4b1 +``` + +## SHA3 + +It contains the following functions: + +1. `sha3_256` +2. `sha3_512` +3. `keccak256` +4. `keccak512` + +Each function calculates the respective digest of a given message. + +- **Parameters:** + + - `msg` (string): The message to calculate the digest + +- **Returns:** A table containing functions to get digest in different formats. + - `asBytes()`: The digest as byte table. + - `asHex()`: The digest as string in hexadecimal format. + - `asString()`: The digest as string format. + +Example: + +```lua + +local str = "ao" + +crypto.digest.sha3_256(str).asHex() -- 1bbe785577db997a394d5b4555eec9159cb51f235aec07514872d2d436c6e985 +crypto.digest.sha3_512(str).asHex() -- 0c29f053400cb1764ce2ec555f598f497e6fcd1d304ce0125faa03bb724f63f213538f41103072ff62ddee701b52c73e621ed4d2254a3e5e9a803d83435b704d +crypto.digest.keccak256(str).asHex() -- 76da52eec05b749b99d6e62bb52333c1569fe75284e6c82f3de12a4618be00d6 +crypto.digest.keccak512(str).asHex() -- 046fbfad009a12cef9ff00c2aac361d004347b2991c1fa80fba5582251b8e0be8def0283f45f020d4b04ff03ead9f6e7c43cc3920810c05b33b4873b99affdea + +``` + +## Blake2b + +Calculates the Blake2b digest of a given message. + +- **Parameters:** + + - `data` (string): The data to be hashed. + - `outlen` (number): The length of the output hash (optional) **default is 64**. + - `key` (string): The key to be used for hashing (optional) **default is ""**. + +- **Returns:** A table containing functions to get digest in different formats. + - `asBytes()`: The digest as byte table. + - `asHex()`: The digest as string in hexadecimal format. + - `asString()`: The digest as string format. + +Example: + +```lua +local str = "ao" + +crypto.digest.blake2b(str).asHex() -- 576701fd79a126f2c414ef94adf1117c88943700f312679d018c29c378b2c807a3412b4e8d51e191c48fb5f5f54bf1bca29a714dda166797b3baf9ead862ae1d +crypto.digest.blake2b(str, 32).asHex() -- 7050811afc947ba7190bb3c0a7b79b4fba304a0de61d529c8a35bdcbbb5544f4 +crypto.digest.blake2b(str, 32, "secret_key").asHex() -- 203c101980fdf6cf24d78879f2e3db86d73d91f7d60960b642022cd6f87408f8 +``` + +--- + +# Ciphers + +## AES + +The Advanced Encryption Standard (AES) is a symmetric block cipher used to encrypt sensitive information. It has two functions encrypt and decrypt. + +### Encrypt + +Encrypts a given message using the AES algorithm. + +- **Parameters:** + + - `data` (string): The data to be encrypted. + - `key` (string): The key to be used for encryption. + - `iv` (string) optional: The initialization vector to be used for encryption. **default is ""** + - `mode` (string) optional: The mode of operation to be used for encryption. **default is "CBC"**. Available modes are `CBC`, `ECB`, `CFB`, `OFB`, `CTR`. + - `keyLength` (number) optional: The length of the key to use for encryption. **default is 128**. + +- **Returns:** A table containing functions to get encrypted data in different formats. + - `asBytes()`: The encrypted data as byte table. + - `asHex()`: The encrypted data as string in hexadecimal format. + - `asString()`: The encrypted data as string format. + +## Decrypt + +Decrypts a given message using the AES algorithm. + +- **Parameters:** + + - `cipher` (string): Hex Encoded encrypted data. + - `key` (string): The key to be used for decryption. + - `iv` (string) optional: The initialization vector to be used for decryption. **default is ""** + - `mode` (string) optional: The mode of operation to be used for decryption. **default is "CBC"**. Available modes are `CBC`, `ECB`, `CFB`, `OFB`, `CTR`. + - `keyLength` (number) optional: The length of the key to use for decryption. **default is 128**. + +- **Returns:** A table containing functions to get decrypted data in different formats. + - `asBytes()`: The decrypted data as byte table. + - `asHex()`: The decrypted data as string in hexadecimal format. + - `asString()`: The decrypted data as string format. + +Example: + +```lua +local str = "ao" + +local iv = "super_secret_shh" +local key_128 = "super_secret_shh" + +local encrypted = crypto.cipher.aes.encrypt("ao", key, iv).asHex() -- A3B9E6E1FBD9D46930E5F76807C84B8E +local decrypted = crypto.cipher.aes.decrypt(encrypted, key, iv).asHex() -- 616F0000000000000000000000000000 + +crypto.utils.hex.hexToString(decrypted) -- ao + +``` + +## ISSAC Cipher + +ISAAC is a cryptographically secure pseudo-random number generator (CSPRNG) and stream cipher. It has the following functions + +1. `seedIsaac`: Seeds the ISAAC cipher with a given seed. +2. `getRandomChar`: Generates a random character using the ISAAC cipher. +3. `random`: Generates a random number between a given range using the ISAAC cipher. +4. `getRandom`: Generates a random number using the ISAAC cipher. +5. `encrypt`: Encrypts a given message using the ISAAC cipher. +6. `decrypt`: Decrypts a given message using the ISAAC cipher. + +### Encrypt + +Encrypts a given message using the ISAAC cipher. + +- **Parameters:** + - `msg` (string): The message to be encrypted. + - `key` (string): The key to be used for encryption. +- **Returns:** A table containing functions to get encrypted data in different formats. + - `asBytes()`: The encrypted data as byte table. + - `asHex()`: The encrypted data as string in hexadecimal format. + - `asString()`: The encrypted data as string format. + +### Decrypt + +Decrypts a given message using the ISAAC cipher. + +- **Parameters:** + - `cipher` (string): Hex Encoded encrypted data. + - `key` (string): Key to be used for decryption. +- **Returns:** A table containing functions to get decrypted data in different formats. + - `asBytes()`: The decrypted data as byte table. + - `asHex()`: The decrypted data as string in hexadecimal format. + - `asString()`: The decrypted data as string format. + +Example: + +```lua +local message = "ao"; +local key = "secret_key"; + +local encrypted = crypto.cipher.issac.encrypt(message, key) +local decrypted = crypto.cipher.issac.decrypt(encrypted.asString(), key) -- ao + + +encrypted.asHex() -- 7851 +``` + +### random + +Generates a random number using the ISAAC cipher. + +- **Parameters:** + - `min` (number) optional: The minimum value of the random number. **defaults to 0**. + - `max` (number) optional: The maximum value of the random number. **defaults to 2^31 - 1**. + - `seed` (string) optional: The seed to be used for generating the random number. **defaults to math.random(0,2^32 - 1)**. +- **Returns:** A random number between the given range. + +Example: + +```lua +crypto.cipher.issac.random(0, 100) -- 42 +``` + +## Morus Cipher + +MORUS is a high-performance authenticated encryption algorithm submitted to the CAESAR competition, and recently selected as a finalist. + +### Encrypt + +Encrypts a given message using the MORUS cipher. + +- **Parameters:** + - `key` (string): The encryption key (16 or 32-byte string). + - `iv` (string): The nonce or initial value (16-byte string). + - `msg` (string): The message to encrypt (variable length string). + - `ad` (string) optional: The additional data (variable length string). **defaults to ""**. +- **Returns:** A table containing functions to get encrypted data in different formats. + - `asBytes()`: The encrypted data as byte table. + - `asHex()`: The encrypted data as string in hexadecimal format. + - `asString()`: The encrypted data as string format. + +### Decrypt + +Decrypts a given message using the MORUS cipher. + +- **Parameters:** + - `key` (string): The encryption key (16 or 32-byte string). + - `iv` (string): The nonce or initial value (16-byte string). + - `cipher` (string): The encrypted message (variable length string). + - `adLen` (number) optional: The length of the additional data (variable length string). **defaults to 0**. +- **Returns:** A table containing functions to get decrypted data in different formats. + - `asBytes()`: The decrypted data as byte table. + - `asHex()`: The decrypted data as string in hexadecimal format. + - `asString()`: The decrypted data as string format. + +Example: + +```lua +local m = "ao" +local k = "super_secret_shh" +local iv = "0000000000000000" +local ad= "" + +local e = crypto.cipher.morus.encrypt(k, iv, m, ad) +local d = crypto.cipher.morus.decrypt(k, iv, e.asString(), #ad) -- ao + +e.asHex() -- 514ed31473d8fb0b76c6cbb17af35ed01d0a +``` + +## NORX Cipher + +NORX is an authenticated encryption scheme with associated data that was selected, along with 14 other primitives, for the third phase of the ongoing CAESAR competition. It is based on the sponge construction and relies on a simple permutation that allows efficient and versatile implementations. + +### Encrypt + +Encrypts a given message using the NORX cipher. + +- **Parameters:** + - `key` (string): The encryption key (32-byte string). + - `nonce` (string): The nonce or initial value (32-byte string). + - `plain` (string): The message to encrypt (variable length string). + - `header` (string) optional: The additional data (variable length string). **defaults to ""**. + - `trailer` (string) optional: The additional data (variable length string). **defaults to ""**. +- **Returns:** A table containing functions to get encrypted data in different formats. + - `asBytes()`: The encrypted data as byte table. + - `asHex()`: The encrypted data as string in hexadecimal format. + - `asString()`: The encrypted data as string format. + +### Decrypt + +Decrypts a given message using the NORX cipher. + +- **Parameters:** + - `key` (string): The encryption key (32-byte string). + - `nonce` (string): The nonce or initial value (32-byte string). + - `crypted` (string): The encrypted message (variable length string). + - `header` (string) optional: The additional data (variable length string). **defaults to ""**. + - `trailer` (string) optional: The additional data (variable length string). **defaults to ""**. +- **Returns:** A table containing functions to get decrypted data in different formats. + - `asBytes()`: The decrypted data as byte table. + - `asHex()`: The decrypted data as string in hexadecimal format. + - `asString()`: The decrypted data as string format. + +Example: + +```lua +local key = "super_duper_secret_password_shhh" +local nonce = "00000000000000000000000000000000" + +local data = "ao" + +-- Header and trailer are optional +local header, trailer = data, data + +local encrypted = crypto.cipher.norx.encrypt(key, nonce, data, header, trailer).asString() +local decrypted = crypto.cipher.norx.decrypt(key, nonce, encrypted, header, trailer) -- ao + +local authTag = encrypted:sub(#encrypted-32+1) + +crypto.utils.hex.stringToHex(encrypted) -- 0bb35a06938e6541eccd4440adb7b46118535f60b09b4adf378807a53df19fc4ea28 +crypto.utils.hex.stringToHex(authTag) -- 5a06938e6541eccd4440adb7b46118535f60b09b4adf378807a53df19fc4ea28 +``` + +--- + +# Random Number Generators + +The module contains a random number generator using ISAAC which is a cryptographically secure pseudo-random number generator (CSPRNG) and stream cipher. + +- **Parameters:** + - `min` (number) optional: The minimum value of the random number. **defaults to 0**. + - `max` (number) optional: The maximum value of the random number. **defaults to 2^31 - 1**. + - `seed` (string) optional: The seed to be used for generating the random number. **defaults to math.random(0,2^32 - 1)**. +- **Returns:** A random number between the given range. + +Example: + +```lua +crypto.random.(0, 100, "seed") -- 42 +``` + +--- + +# MACs + +## HMAC + +The Hash-based Message Authentication Code (HMAC) is a mechanism for message authentication using cryptographic hash functions. HMAC can be used with any iterative cryptographic hash function, e.g., MD5, SHA-1, in combination with a secret shared key. + +The modules exposes a function called `createHmac` which is used to create a HMAC instance. + +- **Parameters:** + - `data` (Stream): The data to be hashed. + - `key` (Array): The key to be used for hashing. + - `algorithm` (string) optional: The algorithm to be used for hashing. **default is "sha256"**. Available algorithms are "sha1", "sha256". **default is "sha1"**. +- **Returns:** A table containing functions to get HMAC in different formats. + - `asBytes()`: The HMAC as byte table. + - `asHex()`: The HMAC as string in hexadecimal format. + - `asString()`: The HMAC as string format. + +Example: + +```lua +local data = crypto.utils.stream.fromString("ao") +local key = crypto.utils.array.fromString("super_secret_key") + +crypto.mac.createHmac(data, key).asHex() -- 3966f45acb53f7a1a493bae15afecb1a204fa32d +crypto.mac.createHmac(data, key, "sha256").asHex() -- 542da02a324155d688c7689669ff94c6a5f906892aa8eccd7284f210ac66e2a7 +``` + +--- + +# KDFs + +## PBKDF2 + +The Password-Based Key Derivation Function 2 (PBKDF2) applies a pseudorandom function, such as hash-based message authentication code (HMAC), to the input password or passphrase along with a salt value and repeats the process many times to produce a derived key, which can then be used as a cryptographic key in subsequent operations. + +- **Parameters:** + - `password` (Array): The password to derive the key from. + - `salt` (Array): The salt to use. + - `iterations` (number): The number of iterations to perform. + - `keyLen` (number): The length of the key to derive. + - `digest` (string) optional: The digest algorithm to use. **default is "sha1"**. Available algorithms are "sha1", "sha256". +- **Returns:** A table containing functions to get derived key in different formats. + - `asBytes()`: The derived key as byte table. + - `asHex()`: The derived key as string in hexadecimal format. + - `asString()`: The derived key as string format. + +Example: + +```lua +local salt = crypto.utils.array.fromString("salt") +local password = crypto.utils.array.fromString("password") +local iterations = 4 +local keyLen = 16 + +local res = crypto.kdf.pbkdf2(password, salt, iterations, keyLen).asHex() -- C4C21BF2BBF61541408EC2A49C89B9C6 +``` + +--- + +# Utilities + +## Array + +Example Usage: + +```lua + +local arr = crypto.utils.array + +arr.fromString("ao") -- Array +arr.toString(arr.fromString("ao")) -- ao + +arr.fromHex("616f") -- Array +arr.toHex(arr.fromHex("616f")) -- 616f + +arr.concat(arr.fromString("a"), arr.fromString("o")) -- Array +arr.truncate(arr.fromString("ao"), 1) -- Array + +arr.XOR(arr.fromString("a"), arr.fromString("o")) -- Array + +arr.substitute(arr.fromString("a"), arr.fromString("o")) -- Array +arr.permute(arr.fromString("a"), arr.fromString("o")) -- Array + +arr.copy(arr.fromString("ao")) -- Array +arr.slice(arr.fromString("ao"), 0, 1) -- Array +``` + +### `size` + +Returns the size of the array. + +- **Parameters:** + - `arr` (Array): The array to get the size of. +- **Returns:** The size of the array. + +### `fromString` + +Creates an array from a string. + +- **Parameters:** + - `str` (string): The string to create the array from. +- **Returns:** The array created from the string. + +### `toString` + +Converts an array to a string. + +- **Parameters:** + - `arr` (Array): The array to convert to a string. +- **Returns:** The array as a string. + +### `fromStream` + +Creates an array from a stream. + +- **Parameters:** + - `stream` (Stream): The stream to create the array from. +- **Returns:** The array created from the stream. + +### `readFromQueue` + +Reads data from a queue and stores it in the array. + +- **Parameters:** + - `queue` (Queue): The queue to read data from. + - `size` (number): The size of the data to read. +- **Returns:** The array containing the data read from the queue. + +### `writeToQueue` + +Writes data from the array to a queue. + +- **Parameters:** + - `queue` (Queue): The queue to write data to. + - `array` (Array): The array to write data from. +- **Returns:** None + +### `toStream` + +Converts an array to a stream. + +- **Parameters:** + - `arr` (Array): The array to convert to a stream. +- **Returns:** (Stream) The array as a stream. + +### `fromHex` + +Creates an array from a hexadecimal string. + +- **Parameters:** + - `hex` (string): The hexadecimal string to create the array from. +- **Returns:** The array created from the hexadecimal string. + +### `toHex` + +Converts an array to a hexadecimal string. + +- **Parameters:** + - `arr` (Array): The array to convert to a hexadecimal string. +- **Returns:** The array as a hexadecimal string. + +### `concat` + +Concatenates two arrays. + +- **Parameters:** + - `a` (Array): The array to concatenate with. + - `b` (Array): The array to concatenate. +- **Returns:** The concatenated array. + +### `truncate` + +Truncates an array to a given length. + +- **Parameters:** + - `a` (Array): The array to truncate. + - `newSize` (number): The new size of the array. +- **Returns:** The truncated array. + +### `XOR` + +Performs a bitwise XOR operation on two arrays. + +- **Parameters:** + - `a` (Array): The first array. + - `b` (Array): The second array. +- **Returns:** The result of the XOR operation. + +### `substitute` + +Creates a new array with keys of first array and values of second + +- **Parameters:** + - `input` (Array): The array to substitute. + - `sbox` (Array): The array to substitute with. +- **Returns:** The substituted array. + +### `permute` + +Creates a new array with keys of second array and values of first array. + +- **Parameters:** + - `input` (Array): The array to permute. + - `pbox` (Array): The array to permute with. +- **Returns:** The permuted array. + +### `copy` + +Creates a copy of an array. + +- **Parameters:** + - `input` (Array): The array to copy. +- **Returns:** The copied array. + +### `slice` + +Creates a slice of an array. + +- **Parameters:** + - `input` (Array): The array to slice. + - `start` (number): The start index of the slice. + - `stop` (number): The end index of the slice. +- **Returns:** The sliced array. + +--- + +## Stream + +Stream is a data structure that represents a sequence of bytes. It is used to store and manipulate data in a streaming fashion. + +Example Usage: + +```lua +local stream = crypto.utils.stream + +local str = "ao" +local arr = {97, 111} + +stream.fromString(str) -- Stream +stream.toString(stream.fromString(str)) -- ao + +stream.fromArray(arr) -- Stream +stream.toArray(stream.fromArray(arr)) -- {97, 111} + +stream.fromHex("616f") -- Stream +stream.toHex(stream.fromHex("616f")) -- 616f +``` + +### `fromString` + +Creates a stream from a string. + +- **Parameters:** + - `str` (string): The string to create the stream from. +- **Returns:** The stream created from the string. + +### `toString` + +Converts a stream to a string. + +- **Parameters:** + - `stream` (Stream): The stream to convert to a string. +- **Returns:** The stream as a string. + +### `fromArray` + +Creates a stream from an array. + +- **Parameters:** + - `arr` (Array): The array to create the stream from. +- **Returns:** The stream created from the array. + +### `toArray` + +Converts a stream to an array. + +- **Parameters:** + - `stream` (Stream): The stream to convert to an array. +- **Returns:** The stream as an array. + +### `fromHex` + +Creates a stream from a hexadecimal string. + +- **Parameters:** + - `hex` (string): The hexadecimal string to create the stream from. +- **Returns:** The stream created from the hexadecimal string. + +### `toHex` + +Converts a stream to a hexadecimal string. + +- **Parameters:** + - `stream` (Stream): The stream to convert to a hexadecimal string. +- **Returns:** The stream as a hexadecimal string. + +--- + +## Hex + +Example Usage: + +```lua +local hex = crypto.utils.hex + +hex.hexToString("616f") -- ao +hex.stringToHex("ao") -- 616f +``` + +### `hexToString` + +Converts a hexadecimal string to a string. + +- **Parameters:** + - `hex` (string): The hexadecimal string to convert to a string. +- **Returns:** The hexadecimal string as a string. + +### `stringToHex` + +Converts a string to a hexadecimal string. + +- **Parameters:** + - `str` (string): The string to convert to a hexadecimal string. +- **Returns:** The string as a hexadecimal string. + +--- + +## Queue + +Queue is a data structure that represents a sequence of elements. It is used to store and manipulate data in a first-in, first-out (FIFO) fashion. + +Example Usage: + +```lua +local q = crypto.utils.queue() + +q.push(1) +q.push(2) +q.pop() -- 1 +q.size() -- 1 +q.getHead() -- 2 +q.getTail() -- 2 +q.reset() +``` + +### `push` + +Pushes an element to the queue. + +- **Parameters:** + - `queue` (Queue): The queue to push the element to. + - `element` (any): The element to push to the queue. +- **Returns:** None + +### `pop` + +Pops an element from the queue. + +- **Parameters:** + - `queue` (Queue): The queue to pop the element from. + - `element` (any): The element to pop from the queue. +- **Returns:** The popped element. + +### `size` + +Returns the size of the queue. + +- **Parameters:** None +- **Returns:** The size of the queue. + +### `getHead` + +Returns the head of the queue. + +- **Parameters:** None +- **Returns:** The head of the queue. + +### `getTail` + +Returns the tail of the queue. + +- **Parameters:** None +- **Returns:** The tail of the queue. + +### `reset` + +Resets the queue. + +- **Parameters:** None + +--- + +## Conventions and Requirements + +1. The module should be imported using `local crypto = require('.crypto')`. +2. The module should be used in a Lua environment. + +--- + +## License + +MIT diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes.lua new file mode 100644 index 0000000..5ed1f5d --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes.lua @@ -0,0 +1,142 @@ +local Stream = require(".crypto.util.stream") +local Hex = require(".crypto.util.hex") +local Array = require(".crypto.util.array") + +-- Ciphers +local AES128Cipher = require(".crypto.cipher.aes128") +local AES192Cipher = require(".crypto.cipher.aes192") +local AES256Cipher = require(".crypto.cipher.aes256") + +-- Modes +local CBCMode = require(".crypto.cipher.mode.cbc") +local ECBMode = require(".crypto.cipher.mode.ecb") +local CFBMode = require(".crypto.cipher.mode.cfb") +local OFBMode = require(".crypto.cipher.mode.ofb") +local CTRMode = require(".crypto.cipher.mode.ctr") + +-- Padding +local ZeroPadding = require(".crypto.padding.zero") + +local public = {} + +local getBlockCipher = function(keyLength) + if keyLength == 128 then + return AES128Cipher + elseif keyLength == 192 then + return AES192Cipher + elseif keyLength == 256 then + return AES256Cipher + elseif keyLength == nil then + return AES128Cipher + else + return nil + end +end + +local getMode = function(mode) + if mode == "CBC" then + return CBCMode + elseif mode == "ECB" then + return ECBMode + elseif mode == "CFB" then + return CFBMode + elseif mode == "OFB" then + return OFBMode + elseif mode == "CTR" then + return CTRMode + else + return nil + end +end + + +--- Encrypts the given data using AES encryption. +--- @param data string - The data to be encrypted. +--- @param key string - The key to use for encryption. +--- @param iv? string (optional) - The initialization vector to use for encryption. Defaults to 16 null bytes. +--- @param mode? string (optional) - The mode to use for encryption. Defaults to "CBC". +--- @param keyLength? number (optional) - The length of the key to use for encryption. Defaults to 128. +--- @returns table - A table containing the encrypted data in bytes, hex, and string formats. +public.encrypt = function(data, key, iv, mode, keyLength) + local d = Array.fromString(data) + local k = Array.fromString(key) + local _iv = iv ~= nil and Array.fromString(iv) or Array.fromHex("00000000000000000000000000000000") + + local cipherMode = getMode(mode) or CBCMode + local blockCipher = getBlockCipher(keyLength) or AES128Cipher + + local cipher = cipherMode.Cipher() + .setKey(k) + .setBlockCipher(blockCipher) + .setPadding(ZeroPadding); + + + local cipherOutput = cipher + .init() + .update(Stream.fromArray(_iv)) + .update(Stream.fromArray(d)) + .finish() + + local results = {} + + results.asBytes = function() + return cipherOutput.asBytes() + end + + results.asHex = function() + return cipherOutput.asHex() + end + + results.asString = function() + return cipherOutput.asString() + end + + return results +end + +--- Decrypts the given data using AES decryption. +--- @param cipher string - The hex encoded cipher to be decrypted. +--- @param key string - The key to use for decryption. +--- @param iv? string (optional) - The initialization vector to use for decryption. Defaults to 16 null bytes. +--- @param mode? string (optional) - The mode to use for decryption. Defaults to "CBC". +--- @param keyLength? number (optional) - The length of the key to use for decryption. Defaults to 128. +public.decrypt = function(cipher, key, iv, mode, keyLength) + local cipherText = Array.fromHex(cipher) + local k = Array.fromString(key) + local _iv = iv ~= nil and Array.fromString(iv) or Array.fromHex("00000000000000000000000000000000") + + local cipherMode = getMode(mode) or CBCMode + local blockCipher = getBlockCipher(keyLength) or AES128Cipher + + + local decipher = cipherMode.Decipher() + .setKey(k) + .setBlockCipher(blockCipher) + .setPadding(ZeroPadding); + + + local plainOutput = decipher + .init() + .update(Stream.fromArray(_iv)) + .update(Stream.fromArray(cipherText)) + .finish() + + local results = {} + + results.asBytes = function() + return plainOutput.asBytes() + end + + results.asHex = function() + return plainOutput.asHex() + end + + results.asString = function() + return plainOutput.asString() + end + + return results +end + + +return public diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes128.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes128.lua new file mode 100644 index 0000000..061598e --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes128.lua @@ -0,0 +1,415 @@ +local Array = require(".crypto.util.array"); +local Bit = require(".crypto.util.bit"); + +local XOR = Bit.bxor; + +local SBOX = { + [0] = 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16}; + +local ISBOX = { + [0] = 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, + 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, + 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, + 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, + 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, + 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, + 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, + 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, + 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, + 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, + 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, + 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, + 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, + 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, + 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D}; + +local ROW_SHIFT = { 1, 6, 11, 16, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, }; +local IROW_SHIFT = { 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, 16, 13, 10, 7, 4, }; + +local ETABLE = { + [0] = 0x01, 0x03, 0x05, 0x0F, 0x11, 0x33, 0x55, 0xFF, 0x1A, 0x2E, 0x72, 0x96, 0xA1, 0xF8, 0x13, 0x35, + 0x5F, 0xE1, 0x38, 0x48, 0xD8, 0x73, 0x95, 0xA4, 0xF7, 0x02, 0x06, 0x0A, 0x1E, 0x22, 0x66, 0xAA, + 0xE5, 0x34, 0x5C, 0xE4, 0x37, 0x59, 0xEB, 0x26, 0x6A, 0xBE, 0xD9, 0x70, 0x90, 0xAB, 0xE6, 0x31, + 0x53, 0xF5, 0x04, 0x0C, 0x14, 0x3C, 0x44, 0xCC, 0x4F, 0xD1, 0x68, 0xB8, 0xD3, 0x6E, 0xB2, 0xCD, + 0x4C, 0xD4, 0x67, 0xA9, 0xE0, 0x3B, 0x4D, 0xD7, 0x62, 0xA6, 0xF1, 0x08, 0x18, 0x28, 0x78, 0x88, + 0x83, 0x9E, 0xB9, 0xD0, 0x6B, 0xBD, 0xDC, 0x7F, 0x81, 0x98, 0xB3, 0xCE, 0x49, 0xDB, 0x76, 0x9A, + 0xB5, 0xC4, 0x57, 0xF9, 0x10, 0x30, 0x50, 0xF0, 0x0B, 0x1D, 0x27, 0x69, 0xBB, 0xD6, 0x61, 0xA3, + 0xFE, 0x19, 0x2B, 0x7D, 0x87, 0x92, 0xAD, 0xEC, 0x2F, 0x71, 0x93, 0xAE, 0xE9, 0x20, 0x60, 0xA0, + 0xFB, 0x16, 0x3A, 0x4E, 0xD2, 0x6D, 0xB7, 0xC2, 0x5D, 0xE7, 0x32, 0x56, 0xFA, 0x15, 0x3F, 0x41, + 0xC3, 0x5E, 0xE2, 0x3D, 0x47, 0xC9, 0x40, 0xC0, 0x5B, 0xED, 0x2C, 0x74, 0x9C, 0xBF, 0xDA, 0x75, + 0x9F, 0xBA, 0xD5, 0x64, 0xAC, 0xEF, 0x2A, 0x7E, 0x82, 0x9D, 0xBC, 0xDF, 0x7A, 0x8E, 0x89, 0x80, + 0x9B, 0xB6, 0xC1, 0x58, 0xE8, 0x23, 0x65, 0xAF, 0xEA, 0x25, 0x6F, 0xB1, 0xC8, 0x43, 0xC5, 0x54, + 0xFC, 0x1F, 0x21, 0x63, 0xA5, 0xF4, 0x07, 0x09, 0x1B, 0x2D, 0x77, 0x99, 0xB0, 0xCB, 0x46, 0xCA, + 0x45, 0xCF, 0x4A, 0xDE, 0x79, 0x8B, 0x86, 0x91, 0xA8, 0xE3, 0x3E, 0x42, 0xC6, 0x51, 0xF3, 0x0E, + 0x12, 0x36, 0x5A, 0xEE, 0x29, 0x7B, 0x8D, 0x8C, 0x8F, 0x8A, 0x85, 0x94, 0xA7, 0xF2, 0x0D, 0x17, + 0x39, 0x4B, 0xDD, 0x7C, 0x84, 0x97, 0xA2, 0xFD, 0x1C, 0x24, 0x6C, 0xB4, 0xC7, 0x52, 0xF6, 0x01}; + +local LTABLE = { + [0] = 0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1A, 0xC6, 0x4B, 0xC7, 0x1B, 0x68, 0x33, 0xEE, 0xDF, 0x03, + 0x64, 0x04, 0xE0, 0x0E, 0x34, 0x8D, 0x81, 0xEF, 0x4C, 0x71, 0x08, 0xC8, 0xF8, 0x69, 0x1C, 0xC1, + 0x7D, 0xC2, 0x1D, 0xB5, 0xF9, 0xB9, 0x27, 0x6A, 0x4D, 0xE4, 0xA6, 0x72, 0x9A, 0xC9, 0x09, 0x78, + 0x65, 0x2F, 0x8A, 0x05, 0x21, 0x0F, 0xE1, 0x24, 0x12, 0xF0, 0x82, 0x45, 0x35, 0x93, 0xDA, 0x8E, + 0x96, 0x8F, 0xDB, 0xBD, 0x36, 0xD0, 0xCE, 0x94, 0x13, 0x5C, 0xD2, 0xF1, 0x40, 0x46, 0x83, 0x38, + 0x66, 0xDD, 0xFD, 0x30, 0xBF, 0x06, 0x8B, 0x62, 0xB3, 0x25, 0xE2, 0x98, 0x22, 0x88, 0x91, 0x10, + 0x7E, 0x6E, 0x48, 0xC3, 0xA3, 0xB6, 0x1E, 0x42, 0x3A, 0x6B, 0x28, 0x54, 0xFA, 0x85, 0x3D, 0xBA, + 0x2B, 0x79, 0x0A, 0x15, 0x9B, 0x9F, 0x5E, 0xCA, 0x4E, 0xD4, 0xAC, 0xE5, 0xF3, 0x73, 0xA7, 0x57, + 0xAF, 0x58, 0xA8, 0x50, 0xF4, 0xEA, 0xD6, 0x74, 0x4F, 0xAE, 0xE9, 0xD5, 0xE7, 0xE6, 0xAD, 0xE8, + 0x2C, 0xD7, 0x75, 0x7A, 0xEB, 0x16, 0x0B, 0xF5, 0x59, 0xCB, 0x5F, 0xB0, 0x9C, 0xA9, 0x51, 0xA0, + 0x7F, 0x0C, 0xF6, 0x6F, 0x17, 0xC4, 0x49, 0xEC, 0xD8, 0x43, 0x1F, 0x2D, 0xA4, 0x76, 0x7B, 0xB7, + 0xCC, 0xBB, 0x3E, 0x5A, 0xFB, 0x60, 0xB1, 0x86, 0x3B, 0x52, 0xA1, 0x6C, 0xAA, 0x55, 0x29, 0x9D, + 0x97, 0xB2, 0x87, 0x90, 0x61, 0xBE, 0xDC, 0xFC, 0xBC, 0x95, 0xCF, 0xCD, 0x37, 0x3F, 0x5B, 0xD1, + 0x53, 0x39, 0x84, 0x3C, 0x41, 0xA2, 0x6D, 0x47, 0x14, 0x2A, 0x9E, 0x5D, 0x56, 0xF2, 0xD3, 0xAB, + 0x44, 0x11, 0x92, 0xD9, 0x23, 0x20, 0x2E, 0x89, 0xB4, 0x7C, 0xB8, 0x26, 0x77, 0x99, 0xE3, 0xA5, + 0x67, 0x4A, 0xED, 0xDE, 0xC5, 0x31, 0xFE, 0x18, 0x0D, 0x63, 0x8C, 0x80, 0xC0, 0xF7, 0x70, 0x07}; + +local MIXTABLE = { + 0x02, 0x03, 0x01, 0x01, + 0x01, 0x02, 0x03, 0x01, + 0x01, 0x01, 0x02, 0x03, + 0x03, 0x01, 0x01, 0x02}; + +local IMIXTABLE = { + 0x0E, 0x0B, 0x0D, 0x09, + 0x09, 0x0E, 0x0B, 0x0D, + 0x0D, 0x09, 0x0E, 0x0B, + 0x0B, 0x0D, 0x09, 0x0E}; + +local RCON = { +[0] = 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, +0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, +0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, +0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, +0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, +0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, +0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, +0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, +0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, +0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, +0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, +0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, +0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, +0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, +0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, +0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d}; + + +local GMUL = function(A, B) + if(A == 0x01) then return B; end + if(B == 0x01) then return A; end + if(A == 0x00) then return 0; end + if(B == 0x00) then return 0; end + + local LA = LTABLE[A]; + local LB = LTABLE[B]; + + local sum = LA + LB; + if (sum > 0xFF) then sum = sum - 0xFF; end + + return ETABLE[sum]; +end + +local byteSub = Array.substitute; + +local shiftRow = Array.permute; + +local mixCol = function(i, mix) + local out = {}; + + local a, b, c, d; + + a = GMUL(i[ 1], mix[ 1]); + b = GMUL(i[ 2], mix[ 2]); + c = GMUL(i[ 3], mix[ 3]); + d = GMUL(i[ 4], mix[ 4]); + out[ 1] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[ 5]); + b = GMUL(i[ 2], mix[ 6]); + c = GMUL(i[ 3], mix[ 7]); + d = GMUL(i[ 4], mix[ 8]); + out[ 2] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[ 9]); + b = GMUL(i[ 2], mix[10]); + c = GMUL(i[ 3], mix[11]); + d = GMUL(i[ 4], mix[12]); + out[ 3] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[13]); + b = GMUL(i[ 2], mix[14]); + c = GMUL(i[ 3], mix[15]); + d = GMUL(i[ 4], mix[16]); + out[ 4] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[ 5], mix[ 1]); + b = GMUL(i[ 6], mix[ 2]); + c = GMUL(i[ 7], mix[ 3]); + d = GMUL(i[ 8], mix[ 4]); + out[ 5] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[ 5]); + b = GMUL(i[ 6], mix[ 6]); + c = GMUL(i[ 7], mix[ 7]); + d = GMUL(i[ 8], mix[ 8]); + out[ 6] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[ 9]); + b = GMUL(i[ 6], mix[10]); + c = GMUL(i[ 7], mix[11]); + d = GMUL(i[ 8], mix[12]); + out[ 7] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[13]); + b = GMUL(i[ 6], mix[14]); + c = GMUL(i[ 7], mix[15]); + d = GMUL(i[ 8], mix[16]); + out[ 8] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[ 9], mix[ 1]); + b = GMUL(i[10], mix[ 2]); + c = GMUL(i[11], mix[ 3]); + d = GMUL(i[12], mix[ 4]); + out[ 9] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[ 5]); + b = GMUL(i[10], mix[ 6]); + c = GMUL(i[11], mix[ 7]); + d = GMUL(i[12], mix[ 8]); + out[10] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[ 9]); + b = GMUL(i[10], mix[10]); + c = GMUL(i[11], mix[11]); + d = GMUL(i[12], mix[12]); + out[11] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[13]); + b = GMUL(i[10], mix[14]); + c = GMUL(i[11], mix[15]); + d = GMUL(i[12], mix[16]); + out[12] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[13], mix[ 1]); + b = GMUL(i[14], mix[ 2]); + c = GMUL(i[15], mix[ 3]); + d = GMUL(i[16], mix[ 4]); + out[13] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[ 5]); + b = GMUL(i[14], mix[ 6]); + c = GMUL(i[15], mix[ 7]); + d = GMUL(i[16], mix[ 8]); + out[14] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[ 9]); + b = GMUL(i[14], mix[10]); + c = GMUL(i[15], mix[11]); + d = GMUL(i[16], mix[12]); + out[15] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[13]); + b = GMUL(i[14], mix[14]); + c = GMUL(i[15], mix[15]); + d = GMUL(i[16], mix[16]); + out[16] = XOR(XOR(a, b), XOR(c, d)); + + return out; +end + +local keyRound = function(key, round) + local out = {}; + + out[ 1] = XOR(key[ 1], XOR(SBOX[key[14]], RCON[round])); + out[ 2] = XOR(key[ 2], SBOX[key[15]]); + out[ 3] = XOR(key[ 3], SBOX[key[16]]); + out[ 4] = XOR(key[ 4], SBOX[key[13]]); + + out[ 5] = XOR(out[ 1], key[ 5]); + out[ 6] = XOR(out[ 2], key[ 6]); + out[ 7] = XOR(out[ 3], key[ 7]); + out[ 8] = XOR(out[ 4], key[ 8]); + + out[ 9] = XOR(out[ 5], key[ 9]); + out[10] = XOR(out[ 6], key[10]); + out[11] = XOR(out[ 7], key[11]); + out[12] = XOR(out[ 8], key[12]); + + out[13] = XOR(out[ 9], key[13]); + out[14] = XOR(out[10], key[14]); + out[15] = XOR(out[11], key[15]); + out[16] = XOR(out[12], key[16]); + + return out; +end + +local keyExpand = function(key) + local keys = {}; + + local temp = key; + + keys[1] = temp; + + for i = 1, 10 do + temp = keyRound(temp, i); + keys[i + 1] = temp; + end + + return keys; + +end + +local addKey = Array.XOR; + + + +local AES = {}; + +AES.blockSize = 16; + +AES.encrypt = function(key, block) + + local keySchedule = keyExpand(key); + + --round 0 + block = addKey(block, keySchedule[1]); + + --round 1 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[2]); + + --round 2 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[3]); + + --round 3 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[4]); + + --round 4 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[5]); + + --round 5 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[6]); + + --round 6 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[7]); + + --round 7 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[8]); + + --round 8 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[9]); + + --round 9 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[10]); + + --round 10 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = addKey(block, keySchedule[11]); + + return block; + +end + +AES.decrypt = function(key, block) + + local keySchedule = keyExpand(key); + + --round 0 + block = addKey(block, keySchedule[11]); + + --round 1 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[10]); + block = mixCol(block, IMIXTABLE); + + --round 2 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[9]); + block = mixCol(block, IMIXTABLE); + + --round 3 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[8]); + block = mixCol(block, IMIXTABLE); + + --round 4 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[7]); + block = mixCol(block, IMIXTABLE); + + --round 5 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[6]); + block = mixCol(block, IMIXTABLE); + + --round 6 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[5]); + block = mixCol(block, IMIXTABLE); + + --round 7 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[4]); + block = mixCol(block, IMIXTABLE); + + --round 8 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[3]); + block = mixCol(block, IMIXTABLE); + + --round 9 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[2]); + block = mixCol(block, IMIXTABLE); + + --round 10 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[1]); + + return block; +end + +return AES; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes192.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes192.lua new file mode 100644 index 0000000..67eb603 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes192.lua @@ -0,0 +1,462 @@ + +local Array = require(".crypto.util.array"); +local Bit = require(".crypto.util.bit"); + +local XOR = Bit.bxor; + +local SBOX = { + [0] = 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16}; + +local ISBOX = { + [0] = 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, + 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, + 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, + 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, + 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, + 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, + 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, + 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, + 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, + 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, + 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, + 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, + 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, + 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, + 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D}; + +local ROW_SHIFT = { 1, 6, 11, 16, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, }; +local IROW_SHIFT = { 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, 16, 13, 10, 7, 4, }; + +local ETABLE = { + [0] = 0x01, 0x03, 0x05, 0x0F, 0x11, 0x33, 0x55, 0xFF, 0x1A, 0x2E, 0x72, 0x96, 0xA1, 0xF8, 0x13, 0x35, + 0x5F, 0xE1, 0x38, 0x48, 0xD8, 0x73, 0x95, 0xA4, 0xF7, 0x02, 0x06, 0x0A, 0x1E, 0x22, 0x66, 0xAA, + 0xE5, 0x34, 0x5C, 0xE4, 0x37, 0x59, 0xEB, 0x26, 0x6A, 0xBE, 0xD9, 0x70, 0x90, 0xAB, 0xE6, 0x31, + 0x53, 0xF5, 0x04, 0x0C, 0x14, 0x3C, 0x44, 0xCC, 0x4F, 0xD1, 0x68, 0xB8, 0xD3, 0x6E, 0xB2, 0xCD, + 0x4C, 0xD4, 0x67, 0xA9, 0xE0, 0x3B, 0x4D, 0xD7, 0x62, 0xA6, 0xF1, 0x08, 0x18, 0x28, 0x78, 0x88, + 0x83, 0x9E, 0xB9, 0xD0, 0x6B, 0xBD, 0xDC, 0x7F, 0x81, 0x98, 0xB3, 0xCE, 0x49, 0xDB, 0x76, 0x9A, + 0xB5, 0xC4, 0x57, 0xF9, 0x10, 0x30, 0x50, 0xF0, 0x0B, 0x1D, 0x27, 0x69, 0xBB, 0xD6, 0x61, 0xA3, + 0xFE, 0x19, 0x2B, 0x7D, 0x87, 0x92, 0xAD, 0xEC, 0x2F, 0x71, 0x93, 0xAE, 0xE9, 0x20, 0x60, 0xA0, + 0xFB, 0x16, 0x3A, 0x4E, 0xD2, 0x6D, 0xB7, 0xC2, 0x5D, 0xE7, 0x32, 0x56, 0xFA, 0x15, 0x3F, 0x41, + 0xC3, 0x5E, 0xE2, 0x3D, 0x47, 0xC9, 0x40, 0xC0, 0x5B, 0xED, 0x2C, 0x74, 0x9C, 0xBF, 0xDA, 0x75, + 0x9F, 0xBA, 0xD5, 0x64, 0xAC, 0xEF, 0x2A, 0x7E, 0x82, 0x9D, 0xBC, 0xDF, 0x7A, 0x8E, 0x89, 0x80, + 0x9B, 0xB6, 0xC1, 0x58, 0xE8, 0x23, 0x65, 0xAF, 0xEA, 0x25, 0x6F, 0xB1, 0xC8, 0x43, 0xC5, 0x54, + 0xFC, 0x1F, 0x21, 0x63, 0xA5, 0xF4, 0x07, 0x09, 0x1B, 0x2D, 0x77, 0x99, 0xB0, 0xCB, 0x46, 0xCA, + 0x45, 0xCF, 0x4A, 0xDE, 0x79, 0x8B, 0x86, 0x91, 0xA8, 0xE3, 0x3E, 0x42, 0xC6, 0x51, 0xF3, 0x0E, + 0x12, 0x36, 0x5A, 0xEE, 0x29, 0x7B, 0x8D, 0x8C, 0x8F, 0x8A, 0x85, 0x94, 0xA7, 0xF2, 0x0D, 0x17, + 0x39, 0x4B, 0xDD, 0x7C, 0x84, 0x97, 0xA2, 0xFD, 0x1C, 0x24, 0x6C, 0xB4, 0xC7, 0x52, 0xF6, 0x01}; + +local LTABLE = { + [0] = 0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1A, 0xC6, 0x4B, 0xC7, 0x1B, 0x68, 0x33, 0xEE, 0xDF, 0x03, + 0x64, 0x04, 0xE0, 0x0E, 0x34, 0x8D, 0x81, 0xEF, 0x4C, 0x71, 0x08, 0xC8, 0xF8, 0x69, 0x1C, 0xC1, + 0x7D, 0xC2, 0x1D, 0xB5, 0xF9, 0xB9, 0x27, 0x6A, 0x4D, 0xE4, 0xA6, 0x72, 0x9A, 0xC9, 0x09, 0x78, + 0x65, 0x2F, 0x8A, 0x05, 0x21, 0x0F, 0xE1, 0x24, 0x12, 0xF0, 0x82, 0x45, 0x35, 0x93, 0xDA, 0x8E, + 0x96, 0x8F, 0xDB, 0xBD, 0x36, 0xD0, 0xCE, 0x94, 0x13, 0x5C, 0xD2, 0xF1, 0x40, 0x46, 0x83, 0x38, + 0x66, 0xDD, 0xFD, 0x30, 0xBF, 0x06, 0x8B, 0x62, 0xB3, 0x25, 0xE2, 0x98, 0x22, 0x88, 0x91, 0x10, + 0x7E, 0x6E, 0x48, 0xC3, 0xA3, 0xB6, 0x1E, 0x42, 0x3A, 0x6B, 0x28, 0x54, 0xFA, 0x85, 0x3D, 0xBA, + 0x2B, 0x79, 0x0A, 0x15, 0x9B, 0x9F, 0x5E, 0xCA, 0x4E, 0xD4, 0xAC, 0xE5, 0xF3, 0x73, 0xA7, 0x57, + 0xAF, 0x58, 0xA8, 0x50, 0xF4, 0xEA, 0xD6, 0x74, 0x4F, 0xAE, 0xE9, 0xD5, 0xE7, 0xE6, 0xAD, 0xE8, + 0x2C, 0xD7, 0x75, 0x7A, 0xEB, 0x16, 0x0B, 0xF5, 0x59, 0xCB, 0x5F, 0xB0, 0x9C, 0xA9, 0x51, 0xA0, + 0x7F, 0x0C, 0xF6, 0x6F, 0x17, 0xC4, 0x49, 0xEC, 0xD8, 0x43, 0x1F, 0x2D, 0xA4, 0x76, 0x7B, 0xB7, + 0xCC, 0xBB, 0x3E, 0x5A, 0xFB, 0x60, 0xB1, 0x86, 0x3B, 0x52, 0xA1, 0x6C, 0xAA, 0x55, 0x29, 0x9D, + 0x97, 0xB2, 0x87, 0x90, 0x61, 0xBE, 0xDC, 0xFC, 0xBC, 0x95, 0xCF, 0xCD, 0x37, 0x3F, 0x5B, 0xD1, + 0x53, 0x39, 0x84, 0x3C, 0x41, 0xA2, 0x6D, 0x47, 0x14, 0x2A, 0x9E, 0x5D, 0x56, 0xF2, 0xD3, 0xAB, + 0x44, 0x11, 0x92, 0xD9, 0x23, 0x20, 0x2E, 0x89, 0xB4, 0x7C, 0xB8, 0x26, 0x77, 0x99, 0xE3, 0xA5, + 0x67, 0x4A, 0xED, 0xDE, 0xC5, 0x31, 0xFE, 0x18, 0x0D, 0x63, 0x8C, 0x80, 0xC0, 0xF7, 0x70, 0x07}; + +local MIXTABLE = { + 0x02, 0x03, 0x01, 0x01, + 0x01, 0x02, 0x03, 0x01, + 0x01, 0x01, 0x02, 0x03, + 0x03, 0x01, 0x01, 0x02}; + +local IMIXTABLE = { + 0x0E, 0x0B, 0x0D, 0x09, + 0x09, 0x0E, 0x0B, 0x0D, + 0x0D, 0x09, 0x0E, 0x0B, + 0x0B, 0x0D, 0x09, 0x0E}; + +local RCON = { +[0] = 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, +0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, +0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, +0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, +0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, +0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, +0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, +0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, +0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, +0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, +0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, +0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, +0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, +0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, +0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, +0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d}; + + +local GMUL = function(A, B) + if(A == 0x01) then return B; end + if(B == 0x01) then return A; end + if(A == 0x00) then return 0; end + if(B == 0x00) then return 0; end + + local LA = LTABLE[A]; + local LB = LTABLE[B]; + + local sum = LA + LB; + if (sum > 0xFF) then sum = sum - 0xFF; end + + return ETABLE[sum]; +end + +local byteSub = Array.substitute; + +local shiftRow = Array.permute; + +local mixCol = function(i, mix) + local out = {}; + + local a, b, c, d; + + a = GMUL(i[ 1], mix[ 1]); + b = GMUL(i[ 2], mix[ 2]); + c = GMUL(i[ 3], mix[ 3]); + d = GMUL(i[ 4], mix[ 4]); + out[ 1] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[ 5]); + b = GMUL(i[ 2], mix[ 6]); + c = GMUL(i[ 3], mix[ 7]); + d = GMUL(i[ 4], mix[ 8]); + out[ 2] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[ 9]); + b = GMUL(i[ 2], mix[10]); + c = GMUL(i[ 3], mix[11]); + d = GMUL(i[ 4], mix[12]); + out[ 3] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[13]); + b = GMUL(i[ 2], mix[14]); + c = GMUL(i[ 3], mix[15]); + d = GMUL(i[ 4], mix[16]); + out[ 4] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[ 5], mix[ 1]); + b = GMUL(i[ 6], mix[ 2]); + c = GMUL(i[ 7], mix[ 3]); + d = GMUL(i[ 8], mix[ 4]); + out[ 5] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[ 5]); + b = GMUL(i[ 6], mix[ 6]); + c = GMUL(i[ 7], mix[ 7]); + d = GMUL(i[ 8], mix[ 8]); + out[ 6] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[ 9]); + b = GMUL(i[ 6], mix[10]); + c = GMUL(i[ 7], mix[11]); + d = GMUL(i[ 8], mix[12]); + out[ 7] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[13]); + b = GMUL(i[ 6], mix[14]); + c = GMUL(i[ 7], mix[15]); + d = GMUL(i[ 8], mix[16]); + out[ 8] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[ 9], mix[ 1]); + b = GMUL(i[10], mix[ 2]); + c = GMUL(i[11], mix[ 3]); + d = GMUL(i[12], mix[ 4]); + out[ 9] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[ 5]); + b = GMUL(i[10], mix[ 6]); + c = GMUL(i[11], mix[ 7]); + d = GMUL(i[12], mix[ 8]); + out[10] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[ 9]); + b = GMUL(i[10], mix[10]); + c = GMUL(i[11], mix[11]); + d = GMUL(i[12], mix[12]); + out[11] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[13]); + b = GMUL(i[10], mix[14]); + c = GMUL(i[11], mix[15]); + d = GMUL(i[12], mix[16]); + out[12] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[13], mix[ 1]); + b = GMUL(i[14], mix[ 2]); + c = GMUL(i[15], mix[ 3]); + d = GMUL(i[16], mix[ 4]); + out[13] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[ 5]); + b = GMUL(i[14], mix[ 6]); + c = GMUL(i[15], mix[ 7]); + d = GMUL(i[16], mix[ 8]); + out[14] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[ 9]); + b = GMUL(i[14], mix[10]); + c = GMUL(i[15], mix[11]); + d = GMUL(i[16], mix[12]); + out[15] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[13]); + b = GMUL(i[14], mix[14]); + c = GMUL(i[15], mix[15]); + d = GMUL(i[16], mix[16]); + out[16] = XOR(XOR(a, b), XOR(c, d)); + + return out; +end + +local keyRound = function(key, round) + local i = (round - 1) * 24; + local out = key; + + out[25 + i] = XOR(key[ 1 + i], XOR(SBOX[key[22 + i]], RCON[round])); + out[26 + i] = XOR(key[ 2 + i], SBOX[key[23 + i]]); + out[27 + i] = XOR(key[ 3 + i], SBOX[key[24 + i]]); + out[28 + i] = XOR(key[ 4 + i], SBOX[key[21 + i]]); + + out[29 + i] = XOR(out[25 + i], key[ 5 + i]); + out[30 + i] = XOR(out[26 + i], key[ 6 + i]); + out[31 + i] = XOR(out[27 + i], key[ 7 + i]); + out[32 + i] = XOR(out[28 + i], key[ 8 + i]); + + out[33 + i] = XOR(out[29 + i], key[ 9 + i]); + out[34 + i] = XOR(out[30 + i], key[10 + i]); + out[35 + i] = XOR(out[31 + i], key[11 + i]); + out[36 + i] = XOR(out[32 + i], key[12 + i]); + + out[37 + i] = XOR(out[33 + i], key[13 + i]); + out[38 + i] = XOR(out[34 + i], key[14 + i]); + out[39 + i] = XOR(out[35 + i], key[15 + i]); + out[40 + i] = XOR(out[36 + i], key[16 + i]); + + out[41 + i] = XOR(out[37 + i], key[17 + i]); + out[42 + i] = XOR(out[38 + i], key[18 + i]); + out[43 + i] = XOR(out[39 + i], key[19 + i]); + out[44 + i] = XOR(out[40 + i], key[20 + i]); + + out[45 + i] = XOR(out[41 + i], key[21 + i]); + out[46 + i] = XOR(out[42 + i], key[22 + i]); + out[47 + i] = XOR(out[43 + i], key[23 + i]); + out[48 + i] = XOR(out[44 + i], key[24 + i]); + + return out; +end + +local keyExpand = function(key) + local bytes = Array.copy(key); + + for i = 1, 8 do + keyRound(bytes, i); + end + + local keys = {}; + + keys[ 1] = Array.slice(bytes, 1, 16); + keys[ 2] = Array.slice(bytes, 17, 32); + keys[ 3] = Array.slice(bytes, 33, 48); + keys[ 4] = Array.slice(bytes, 49, 64); + keys[ 5] = Array.slice(bytes, 65, 80); + keys[ 6] = Array.slice(bytes, 81, 96); + keys[ 7] = Array.slice(bytes, 97, 112); + keys[ 8] = Array.slice(bytes, 113, 128); + keys[ 9] = Array.slice(bytes, 129, 144); + keys[10] = Array.slice(bytes, 145, 160); + keys[11] = Array.slice(bytes, 161, 176); + keys[12] = Array.slice(bytes, 177, 192); + keys[13] = Array.slice(bytes, 193, 208); + + return keys; + +end + +local addKey = Array.XOR; + + + +local AES = {}; + +AES.blockSize = 16; + +AES.encrypt = function(key, block) + + local keySchedule = keyExpand(key); + + --round 0 + block = addKey(block, keySchedule[1]); + + --round 1 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[2]); + + --round 2 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[3]); + + --round 3 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[4]); + + --round 4 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[5]); + + --round 5 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[6]); + + --round 6 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[7]); + + --round 7 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[8]); + + --round 8 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[9]); + + --round 9 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[10]); + + --round 10 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[11]); + + --round 11 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[12]); + + --round 12 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = addKey(block, keySchedule[13]); + + return block; + +end + +AES.decrypt = function(key, block) + + local keySchedule = keyExpand(key); + + --round 0 + block = addKey(block, keySchedule[13]); + + --round 1 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[12]); + block = mixCol(block, IMIXTABLE); + + --round 2 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[11]); + block = mixCol(block, IMIXTABLE); + + --round 3 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[10]); + block = mixCol(block, IMIXTABLE); + + --round 4 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[9]); + block = mixCol(block, IMIXTABLE); + + --round 5 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[8]); + block = mixCol(block, IMIXTABLE); + + --round 6 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[7]); + block = mixCol(block, IMIXTABLE); + + --round 7 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[6]); + block = mixCol(block, IMIXTABLE); + + --round 8 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[5]); + block = mixCol(block, IMIXTABLE); + + --round 9 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[4]); + block = mixCol(block, IMIXTABLE); + + --round 10 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[3]); + block = mixCol(block, IMIXTABLE); + + --round 11 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[2]); + block = mixCol(block, IMIXTABLE); + + --round 12 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[1]); + + return block; +end + +return AES; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes256.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes256.lua new file mode 100644 index 0000000..bc79e77 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes256.lua @@ -0,0 +1,498 @@ +local Array = require(".crypto.util.array"); +local Bit = require(".crypto.util.bit"); + +local XOR = Bit.bxor; + +local SBOX = { + [0] = 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16}; + +local ISBOX = { + [0] = 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, + 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, + 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, + 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, + 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, + 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, + 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, + 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, + 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, + 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, + 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, + 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, + 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, + 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, + 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D}; + +local ROW_SHIFT = { 1, 6, 11, 16, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, }; +local IROW_SHIFT = { 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, 16, 13, 10, 7, 4, }; + +local ETABLE = { + [0] = 0x01, 0x03, 0x05, 0x0F, 0x11, 0x33, 0x55, 0xFF, 0x1A, 0x2E, 0x72, 0x96, 0xA1, 0xF8, 0x13, 0x35, + 0x5F, 0xE1, 0x38, 0x48, 0xD8, 0x73, 0x95, 0xA4, 0xF7, 0x02, 0x06, 0x0A, 0x1E, 0x22, 0x66, 0xAA, + 0xE5, 0x34, 0x5C, 0xE4, 0x37, 0x59, 0xEB, 0x26, 0x6A, 0xBE, 0xD9, 0x70, 0x90, 0xAB, 0xE6, 0x31, + 0x53, 0xF5, 0x04, 0x0C, 0x14, 0x3C, 0x44, 0xCC, 0x4F, 0xD1, 0x68, 0xB8, 0xD3, 0x6E, 0xB2, 0xCD, + 0x4C, 0xD4, 0x67, 0xA9, 0xE0, 0x3B, 0x4D, 0xD7, 0x62, 0xA6, 0xF1, 0x08, 0x18, 0x28, 0x78, 0x88, + 0x83, 0x9E, 0xB9, 0xD0, 0x6B, 0xBD, 0xDC, 0x7F, 0x81, 0x98, 0xB3, 0xCE, 0x49, 0xDB, 0x76, 0x9A, + 0xB5, 0xC4, 0x57, 0xF9, 0x10, 0x30, 0x50, 0xF0, 0x0B, 0x1D, 0x27, 0x69, 0xBB, 0xD6, 0x61, 0xA3, + 0xFE, 0x19, 0x2B, 0x7D, 0x87, 0x92, 0xAD, 0xEC, 0x2F, 0x71, 0x93, 0xAE, 0xE9, 0x20, 0x60, 0xA0, + 0xFB, 0x16, 0x3A, 0x4E, 0xD2, 0x6D, 0xB7, 0xC2, 0x5D, 0xE7, 0x32, 0x56, 0xFA, 0x15, 0x3F, 0x41, + 0xC3, 0x5E, 0xE2, 0x3D, 0x47, 0xC9, 0x40, 0xC0, 0x5B, 0xED, 0x2C, 0x74, 0x9C, 0xBF, 0xDA, 0x75, + 0x9F, 0xBA, 0xD5, 0x64, 0xAC, 0xEF, 0x2A, 0x7E, 0x82, 0x9D, 0xBC, 0xDF, 0x7A, 0x8E, 0x89, 0x80, + 0x9B, 0xB6, 0xC1, 0x58, 0xE8, 0x23, 0x65, 0xAF, 0xEA, 0x25, 0x6F, 0xB1, 0xC8, 0x43, 0xC5, 0x54, + 0xFC, 0x1F, 0x21, 0x63, 0xA5, 0xF4, 0x07, 0x09, 0x1B, 0x2D, 0x77, 0x99, 0xB0, 0xCB, 0x46, 0xCA, + 0x45, 0xCF, 0x4A, 0xDE, 0x79, 0x8B, 0x86, 0x91, 0xA8, 0xE3, 0x3E, 0x42, 0xC6, 0x51, 0xF3, 0x0E, + 0x12, 0x36, 0x5A, 0xEE, 0x29, 0x7B, 0x8D, 0x8C, 0x8F, 0x8A, 0x85, 0x94, 0xA7, 0xF2, 0x0D, 0x17, + 0x39, 0x4B, 0xDD, 0x7C, 0x84, 0x97, 0xA2, 0xFD, 0x1C, 0x24, 0x6C, 0xB4, 0xC7, 0x52, 0xF6, 0x01}; + +local LTABLE = { + [0] = 0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1A, 0xC6, 0x4B, 0xC7, 0x1B, 0x68, 0x33, 0xEE, 0xDF, 0x03, + 0x64, 0x04, 0xE0, 0x0E, 0x34, 0x8D, 0x81, 0xEF, 0x4C, 0x71, 0x08, 0xC8, 0xF8, 0x69, 0x1C, 0xC1, + 0x7D, 0xC2, 0x1D, 0xB5, 0xF9, 0xB9, 0x27, 0x6A, 0x4D, 0xE4, 0xA6, 0x72, 0x9A, 0xC9, 0x09, 0x78, + 0x65, 0x2F, 0x8A, 0x05, 0x21, 0x0F, 0xE1, 0x24, 0x12, 0xF0, 0x82, 0x45, 0x35, 0x93, 0xDA, 0x8E, + 0x96, 0x8F, 0xDB, 0xBD, 0x36, 0xD0, 0xCE, 0x94, 0x13, 0x5C, 0xD2, 0xF1, 0x40, 0x46, 0x83, 0x38, + 0x66, 0xDD, 0xFD, 0x30, 0xBF, 0x06, 0x8B, 0x62, 0xB3, 0x25, 0xE2, 0x98, 0x22, 0x88, 0x91, 0x10, + 0x7E, 0x6E, 0x48, 0xC3, 0xA3, 0xB6, 0x1E, 0x42, 0x3A, 0x6B, 0x28, 0x54, 0xFA, 0x85, 0x3D, 0xBA, + 0x2B, 0x79, 0x0A, 0x15, 0x9B, 0x9F, 0x5E, 0xCA, 0x4E, 0xD4, 0xAC, 0xE5, 0xF3, 0x73, 0xA7, 0x57, + 0xAF, 0x58, 0xA8, 0x50, 0xF4, 0xEA, 0xD6, 0x74, 0x4F, 0xAE, 0xE9, 0xD5, 0xE7, 0xE6, 0xAD, 0xE8, + 0x2C, 0xD7, 0x75, 0x7A, 0xEB, 0x16, 0x0B, 0xF5, 0x59, 0xCB, 0x5F, 0xB0, 0x9C, 0xA9, 0x51, 0xA0, + 0x7F, 0x0C, 0xF6, 0x6F, 0x17, 0xC4, 0x49, 0xEC, 0xD8, 0x43, 0x1F, 0x2D, 0xA4, 0x76, 0x7B, 0xB7, + 0xCC, 0xBB, 0x3E, 0x5A, 0xFB, 0x60, 0xB1, 0x86, 0x3B, 0x52, 0xA1, 0x6C, 0xAA, 0x55, 0x29, 0x9D, + 0x97, 0xB2, 0x87, 0x90, 0x61, 0xBE, 0xDC, 0xFC, 0xBC, 0x95, 0xCF, 0xCD, 0x37, 0x3F, 0x5B, 0xD1, + 0x53, 0x39, 0x84, 0x3C, 0x41, 0xA2, 0x6D, 0x47, 0x14, 0x2A, 0x9E, 0x5D, 0x56, 0xF2, 0xD3, 0xAB, + 0x44, 0x11, 0x92, 0xD9, 0x23, 0x20, 0x2E, 0x89, 0xB4, 0x7C, 0xB8, 0x26, 0x77, 0x99, 0xE3, 0xA5, + 0x67, 0x4A, 0xED, 0xDE, 0xC5, 0x31, 0xFE, 0x18, 0x0D, 0x63, 0x8C, 0x80, 0xC0, 0xF7, 0x70, 0x07}; + +local MIXTABLE = { + 0x02, 0x03, 0x01, 0x01, + 0x01, 0x02, 0x03, 0x01, + 0x01, 0x01, 0x02, 0x03, + 0x03, 0x01, 0x01, 0x02}; + +local IMIXTABLE = { + 0x0E, 0x0B, 0x0D, 0x09, + 0x09, 0x0E, 0x0B, 0x0D, + 0x0D, 0x09, 0x0E, 0x0B, + 0x0B, 0x0D, 0x09, 0x0E}; + +local RCON = { +[0] = 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, +0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, +0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, +0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, +0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, +0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, +0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, +0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, +0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, +0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, +0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, +0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, +0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, +0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, +0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, +0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d}; + + +local GMUL = function(A, B) + if(A == 0x01) then return B; end + if(B == 0x01) then return A; end + if(A == 0x00) then return 0; end + if(B == 0x00) then return 0; end + + local LA = LTABLE[A]; + local LB = LTABLE[B]; + + local sum = LA + LB; + if (sum > 0xFF) then sum = sum - 0xFF; end + + return ETABLE[sum]; +end + +local byteSub = Array.substitute; + +local shiftRow = Array.permute; + +local mixCol = function(i, mix) + local out = {}; + + local a, b, c, d; + + a = GMUL(i[ 1], mix[ 1]); + b = GMUL(i[ 2], mix[ 2]); + c = GMUL(i[ 3], mix[ 3]); + d = GMUL(i[ 4], mix[ 4]); + out[ 1] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[ 5]); + b = GMUL(i[ 2], mix[ 6]); + c = GMUL(i[ 3], mix[ 7]); + d = GMUL(i[ 4], mix[ 8]); + out[ 2] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[ 9]); + b = GMUL(i[ 2], mix[10]); + c = GMUL(i[ 3], mix[11]); + d = GMUL(i[ 4], mix[12]); + out[ 3] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[13]); + b = GMUL(i[ 2], mix[14]); + c = GMUL(i[ 3], mix[15]); + d = GMUL(i[ 4], mix[16]); + out[ 4] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[ 5], mix[ 1]); + b = GMUL(i[ 6], mix[ 2]); + c = GMUL(i[ 7], mix[ 3]); + d = GMUL(i[ 8], mix[ 4]); + out[ 5] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[ 5]); + b = GMUL(i[ 6], mix[ 6]); + c = GMUL(i[ 7], mix[ 7]); + d = GMUL(i[ 8], mix[ 8]); + out[ 6] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[ 9]); + b = GMUL(i[ 6], mix[10]); + c = GMUL(i[ 7], mix[11]); + d = GMUL(i[ 8], mix[12]); + out[ 7] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[13]); + b = GMUL(i[ 6], mix[14]); + c = GMUL(i[ 7], mix[15]); + d = GMUL(i[ 8], mix[16]); + out[ 8] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[ 9], mix[ 1]); + b = GMUL(i[10], mix[ 2]); + c = GMUL(i[11], mix[ 3]); + d = GMUL(i[12], mix[ 4]); + out[ 9] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[ 5]); + b = GMUL(i[10], mix[ 6]); + c = GMUL(i[11], mix[ 7]); + d = GMUL(i[12], mix[ 8]); + out[10] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[ 9]); + b = GMUL(i[10], mix[10]); + c = GMUL(i[11], mix[11]); + d = GMUL(i[12], mix[12]); + out[11] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[13]); + b = GMUL(i[10], mix[14]); + c = GMUL(i[11], mix[15]); + d = GMUL(i[12], mix[16]); + out[12] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[13], mix[ 1]); + b = GMUL(i[14], mix[ 2]); + c = GMUL(i[15], mix[ 3]); + d = GMUL(i[16], mix[ 4]); + out[13] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[ 5]); + b = GMUL(i[14], mix[ 6]); + c = GMUL(i[15], mix[ 7]); + d = GMUL(i[16], mix[ 8]); + out[14] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[ 9]); + b = GMUL(i[14], mix[10]); + c = GMUL(i[15], mix[11]); + d = GMUL(i[16], mix[12]); + out[15] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[13]); + b = GMUL(i[14], mix[14]); + c = GMUL(i[15], mix[15]); + d = GMUL(i[16], mix[16]); + out[16] = XOR(XOR(a, b), XOR(c, d)); + + return out; +end + +local keyRound = function(key, round) + local i = (round - 1) * 32; + local out = key; + + out[33 + i] = XOR(key[ 1 + i], XOR(SBOX[key[30 + i]], RCON[round])); + out[34 + i] = XOR(key[ 2 + i], SBOX[key[31 + i]]); + out[35 + i] = XOR(key[ 3 + i], SBOX[key[32 + i]]); + out[36 + i] = XOR(key[ 4 + i], SBOX[key[29 + i]]); + + out[37 + i] = XOR(out[33 + i], key[ 5 + i]); + out[38 + i] = XOR(out[34 + i], key[ 6 + i]); + out[39 + i] = XOR(out[35 + i], key[ 7 + i]); + out[40 + i] = XOR(out[36 + i], key[ 8 + i]); + + out[41 + i] = XOR(out[37 + i], key[ 9 + i]); + out[42 + i] = XOR(out[38 + i], key[10 + i]); + out[43 + i] = XOR(out[39 + i], key[11 + i]); + out[44 + i] = XOR(out[40 + i], key[12 + i]); + + out[45 + i] = XOR(out[41 + i], key[13 + i]); + out[46 + i] = XOR(out[42 + i], key[14 + i]); + out[47 + i] = XOR(out[43 + i], key[15 + i]); + out[48 + i] = XOR(out[44 + i], key[16 + i]); + + + out[49 + i] = XOR(SBOX[out[45 + i]], key[17 + i]); + out[50 + i] = XOR(SBOX[out[46 + i]], key[18 + i]); + out[51 + i] = XOR(SBOX[out[47 + i]], key[19 + i]); + out[52 + i] = XOR(SBOX[out[48 + i]], key[20 + i]); + + out[53 + i] = XOR(out[49 + i], key[21 + i]); + out[54 + i] = XOR(out[50 + i], key[22 + i]); + out[55 + i] = XOR(out[51 + i], key[23 + i]); + out[56 + i] = XOR(out[52 + i], key[24 + i]); + + out[57 + i] = XOR(out[53 + i], key[25 + i]); + out[58 + i] = XOR(out[54 + i], key[26 + i]); + out[59 + i] = XOR(out[55 + i], key[27 + i]); + out[60 + i] = XOR(out[56 + i], key[28 + i]); + + out[61 + i] = XOR(out[57 + i], key[29 + i]); + out[62 + i] = XOR(out[58 + i], key[30 + i]); + out[63 + i] = XOR(out[59 + i], key[31 + i]); + out[64 + i] = XOR(out[60 + i], key[32 + i]); + + return out; +end + +local keyExpand = function(key) + local bytes = Array.copy(key); + + for i = 1, 7 do + keyRound(bytes, i); + end + + local keys = {}; + + keys[ 1] = Array.slice(bytes, 1, 16); + keys[ 2] = Array.slice(bytes, 17, 32); + keys[ 3] = Array.slice(bytes, 33, 48); + keys[ 4] = Array.slice(bytes, 49, 64); + keys[ 5] = Array.slice(bytes, 65, 80); + keys[ 6] = Array.slice(bytes, 81, 96); + keys[ 7] = Array.slice(bytes, 97, 112); + keys[ 8] = Array.slice(bytes, 113, 128); + keys[ 9] = Array.slice(bytes, 129, 144); + keys[10] = Array.slice(bytes, 145, 160); + keys[11] = Array.slice(bytes, 161, 176); + keys[12] = Array.slice(bytes, 177, 192); + keys[13] = Array.slice(bytes, 193, 208); + keys[14] = Array.slice(bytes, 209, 224); + keys[15] = Array.slice(bytes, 225, 240); + + return keys; + +end + +local addKey = Array.XOR; + + + +local AES = {}; + +AES.blockSize = 16; + +AES.encrypt = function(key, block) + + local keySchedule = keyExpand(key); + + --round 0 + block = addKey(block, keySchedule[1]); + + --round 1 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[2]); + + --round 2 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[3]); + + --round 3 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[4]); + + --round 4 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[5]); + + --round 5 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[6]); + + --round 6 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[7]); + + --round 7 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[8]); + + --round 8 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[9]); + + --round 9 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[10]); + + --round 10 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[11]); + + --round 11 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[12]); + + --round 12 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[13]); + + --round 13 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, keySchedule[14]); + + --round 14 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = addKey(block, keySchedule[15]); + + return block; + +end + +AES.decrypt = function(key, block) + + local keySchedule = keyExpand(key); + + --round 0 + block = addKey(block, keySchedule[15]); + + --round 1 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[14]); + block = mixCol(block, IMIXTABLE); + + --round 2 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[13]); + block = mixCol(block, IMIXTABLE); + + --round 3 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[12]); + block = mixCol(block, IMIXTABLE); + + --round 4 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[11]); + block = mixCol(block, IMIXTABLE); + + --round 5 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[10]); + block = mixCol(block, IMIXTABLE); + + --round 6 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[9]); + block = mixCol(block, IMIXTABLE); + + --round 7 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[8]); + block = mixCol(block, IMIXTABLE); + + --round 8 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[7]); + block = mixCol(block, IMIXTABLE); + + --round 9 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[6]); + block = mixCol(block, IMIXTABLE); + + --round 10 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[5]); + block = mixCol(block, IMIXTABLE); + + --round 11 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[4]); + block = mixCol(block, IMIXTABLE); + + --round 12 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[3]); + block = mixCol(block, IMIXTABLE); + + --round 13 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[2]); + block = mixCol(block, IMIXTABLE); + + --round 14 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, keySchedule[1]); + + return block; +end + +return AES; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/init.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/init.lua new file mode 100644 index 0000000..72e8e8a --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/init.lua @@ -0,0 +1,14 @@ +local issac = require(".crypto.cipher.issac") +local morus = require(".crypto.cipher.morus") +local aes = require(".crypto.cipher.aes") +local norx = require(".crypto.cipher.norx") + +local cipher = { + _version = "0.0.1", + issac = issac, + morus = morus, + aes = aes, + norx = norx +}; + +return cipher \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/issac.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/issac.lua new file mode 100644 index 0000000..4ec39fc --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/issac.lua @@ -0,0 +1,204 @@ +local Hex = require(".crypto.util.hex"); + +-- External Results +local randRsl = {}; +local randCnt = 0; + +-- Internal State +local mm = {}; +local aa,bb,cc = 0,0,0; + +-- Cap to maintain 32 bit maths +local cap = 0x100000000; + +-- CipherMode +local ENCRYPT = 1; +local DECRYPT = 2; + +local function isaac() + cc = ( cc + 1 ) % cap; -- cc just gets incremented once per 256 results + bb = ( bb + cc ) % cap; -- then combined with bb + + for i = 0,255 do + local x = mm[i]; + local y; + local imod = i % 4; + if imod == 0 then aa = aa ~ (aa << 13); + elseif imod == 1 then aa = aa ~ (aa >> 6); + elseif imod == 2 then aa = aa ~ (aa << 2); + elseif imod == 3 then aa = aa ~ (aa >> 16); + end + aa = ( mm[(i+128)%256] + aa ) % cap; + y = ( mm[(x>>2) % 256] + aa + bb ) % cap; + mm[i] = y; + bb = ( mm[(y>>10)%256] + x ) % cap; + randRsl[i] = bb; + end + + randCnt = 0; -- Prepare to use the first set of results. + +end + +local function mix(a) + a[0] = ( a[0] ~ ( a[1] << 11 ) ) % cap; a[3] = ( a[3] + a[0] ) % cap; a[1] = ( a[1] + a[2] ) % cap; + a[1] = ( a[1] ~ ( a[2] >> 2 ) ) % cap; a[4] = ( a[4] + a[1] ) % cap; a[2] = ( a[2] + a[3] ) % cap; + a[2] = ( a[2] ~ ( a[3] << 8 ) ) % cap; a[5] = ( a[5] + a[2] ) % cap; a[3] = ( a[3] + a[4] ) % cap; + a[3] = ( a[3] ~ ( a[4] >> 16 ) ) % cap; a[6] = ( a[6] + a[3] ) % cap; a[4] = ( a[4] + a[5] ) % cap; + a[4] = ( a[4] ~ ( a[5] << 10 ) ) % cap; a[7] = ( a[7] + a[4] ) % cap; a[5] = ( a[5] + a[6] ) % cap; + a[5] = ( a[5] ~ ( a[6] >> 4 ) ) % cap; a[0] = ( a[0] + a[5] ) % cap; a[6] = ( a[6] + a[7] ) % cap; + a[6] = ( a[6] ~ ( a[7] << 8 ) ) % cap; a[1] = ( a[1] + a[6] ) % cap; a[7] = ( a[7] + a[0] ) % cap; + a[7] = ( a[7] ~ ( a[0] >> 9 ) ) % cap; a[2] = ( a[2] + a[7] ) % cap; a[0] = ( a[0] + a[1] ) % cap; +end + +local function randInit(flag) + + -- The golden ratio in 32 bit + -- math.floor((((math.sqrt(5)+1)/2)%1)*2^32) == 2654435769 == 0x9e3779b9 + local a = { [0] = 0x9e3779b9, 0x9e3779b9, 0x9e3779b9, 0x9e3779b9, 0x9e3779b9, 0x9e3779b9, 0x9e3779b9, 0x9e3779b9, }; + + aa,bb,cc = 0,0,0; + + for i = 1,4 do mix(a) end -- Scramble it. + + for i = 0,255,8 do -- Fill in mm[] with messy stuff. + if flag then -- Use all the information in the seed. + for j = 0,7 do + a[j] = ( a[j] + randRsl[i+j] ) % cap; + end + end + mix(a); + for j = 0,7 do + mm[i+j] = a[j]; + end + end + + if flag then + -- Do a second pass to make all of the seed affect all of mm. + for i = 0,255,8 do + for j = 0,7 do + a[j] = ( a[j] + mm[i+j] ) % cap; + end + mix(a); + for j = 0,7 do + mm[i+j] = a[j]; + end + end + end + + isaac(); -- Fill in the first set of results. + randCnt = 0; -- Prepare to use the first set of results. + +end + +--- Seeds the ISAAC random number generator with the given seed. +--- @param seed string - The seed to use for the random number generator. +--- @param flag? boolean - Whether to use all the information in the seed. Defaults to true. +local function seedIsaac(seed, flag) + local seedLength = #seed; + for i = 0,255 do mm[i] = 0; end + for i = 0,255 do randRsl[i] = seed:byte(i+1,i+1) or 0; end + randInit(flag); +end + +--- Retrieves a random number from the ISAAC random number generator +--- @return number: The random number +local function getRandom() + local result = randRsl[randCnt]; + randCnt = randCnt + 1; + if randCnt > 255 then + isaac(); + randCnt = 0; + end + return result; +end + +--- Get a random 32-bit value within the specified range. +--- @param min? number (optional) - The minimum value of the range. Defaults to 0. +--- @param max? number (optional) - The maximum value of the range. Defaults to 2^31-1. +--- @param seed? string (optional) - The seed to use for the random number generator. +--- @return number: The random 32-bit value within the specified range. +local function random(min, max, seed) + local min = min or 0; + local max = max or 2^31-1; + if seed then + seedIsaac(seed, true); + else + seedIsaac(tostring(math.random(2^31-1)), false); + end + return (getRandom() % (max - min + 1)) + min; +end + + +--- Get a random character in printable ASCII range. +--- @return number: The random character [32, 126]. +local function getRandomChar() + return getRandom() % 95 + 32; +end + +-- Caesar-shift a character places: Generalized Vigenere +local function caesar(m, ch, shift, modulo, start) + local n + local si = 1 + if m == DECRYPT then shift = shift*-1 ; end + n = (ch - start) + shift; + if n < 0 then si,n = -1,n*-1 ; end + n = ( n % modulo ) * si; + if n < 0 then n = n + modulo ; end + return start + n; +end + +--- Encrypts a message using the ISSAC cipher algorithm. +--- @param msg string - The message to be encrypted. +--- @param key string - The key used for encryption. +--- @returns table - A table containing the encrypted message in bytes, string, and hex formats. +local function encrypt(msg, key) + seedIsaac(key, true); + local msgLength = #msg; + local destination = {}; + + for i = 1, msgLength do + destination[i] = string.char(caesar(1, msg:byte(i, i), getRandomChar(), 95, 32)); + end + + local encrypted = destination + + local public = {} + public.asBytes = function() + return encrypted + end + + public.asString = function() + return table.concat(encrypted) + end + + public.asHex = function() + return Hex.stringToHex(table.concat(encrypted)) + end + + return public +end + +--- Decrypts an encrypted message using the ISSAC cipher algorithm. +--- @param encrypted string - The encrypted message to be decrypted. +--- @param key string - The key used for encryption. +--- @returns string - The decrypted message. +local function decrypt(encrypted, key) + seedIsaac(key, true); + local msgLength = #encrypted; + local destination = {}; + + for i = 1, msgLength do + destination[i] = string.char(caesar(2, encrypted:byte(i, i), getRandomChar(), 95, 32)); + end + + return table.concat(destination); +end + +return { + seedIsaac = seedIsaac, + getRandomChar = getRandomChar, + random = random, + getRandom = getRandom, + encrypt = encrypt, + decrypt = decrypt +} \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cbc.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cbc.lua new file mode 100644 index 0000000..2879072 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cbc.lua @@ -0,0 +1,171 @@ +local Array = require(".crypto.util.array"); +local Stream = require(".crypto.util.stream"); +local Queue = require(".crypto.util.queue"); + +local CBC = {}; + +CBC.Cipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = Array.XOR(iv, block); + out = blockCipher.encrypt(key, out); + Array.writeToQueue(outputQueue, out); + iv = out; + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + public.asString = function() + return Stream.toString(outputQueue.pop); + end + + return public; + +end + + +CBC.Decipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = block; + out = blockCipher.decrypt(key, out); + out = Array.XOR(iv, out); + Array.writeToQueue(outputQueue, out); + iv = block; + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + public.asString = function() + return Stream.toString(outputQueue.pop); + end + + return public; + +end + +return CBC; diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cfb.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cfb.lua new file mode 100644 index 0000000..331e915 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cfb.lua @@ -0,0 +1,171 @@ +local Array = require(".crypto.util.array"); +local Stream = require(".crypto.util.stream"); +local Queue = require(".crypto.util.queue"); + +local CFB = {}; + +CFB.Cipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = iv; + out = blockCipher.encrypt(key, out); + out = Array.XOR(out, block); + Array.writeToQueue(outputQueue, out); + iv = out; + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + public.asString = function() + return Stream.toString(outputQueue.pop); + end + + return public; + +end + +CFB.Decipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = iv; + out = blockCipher.encrypt(key, out); + out = Array.XOR(out, block); + Array.writeToQueue(outputQueue, out); + iv = block; + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + public.asString = function() + return Stream.toString(outputQueue.pop); + end + + return public; + +end + +return CFB; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ctr.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ctr.lua new file mode 100644 index 0000000..e999bce --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ctr.lua @@ -0,0 +1,252 @@ +local Array = require(".crypto.util.array"); +local Stream = require(".crypto.util.stream"); +local Queue = require(".crypto.util.queue"); + +local Bit = require(".crypto.util.bit"); + +local AND = Bit.band; + +local CTR = {}; + +CTR.Cipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + local updateIV = function() + iv[16] = iv[16] + 1; + if iv[16] <= 0xFF then return; end + iv[16] = AND(iv[16], 0xFF); + + iv[15] = iv[15] + 1; + if iv[15] <= 0xFF then return; end + iv[15] = AND(iv[15], 0xFF); + + iv[14] = iv[14] + 1; + if iv[14] <= 0xFF then return; end + iv[14] = AND(iv[14], 0xFF); + + iv[13] = iv[13] + 1; + if iv[13] <= 0xFF then return; end + iv[13] = AND(iv[13], 0xFF); + + iv[12] = iv[12] + 1; + if iv[12] <= 0xFF then return; end + iv[12] = AND(iv[12], 0xFF); + + iv[11] = iv[11] + 1; + if iv[11] <= 0xFF then return; end + iv[11] = AND(iv[11], 0xFF); + + iv[10] = iv[10] + 1; + if iv[10] <= 0xFF then return; end + iv[10] = AND(iv[10], 0xFF); + + iv[9] = iv[9] + 1; + if iv[9] <= 0xFF then return; end + iv[9] = AND(iv[9], 0xFF); + + return; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = iv; + out = blockCipher.encrypt(key, out); + + out = Array.XOR(out, block); + Array.writeToQueue(outputQueue, out); + updateIV(); + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + public.asString = function() + return Stream.toString(outputQueue.pop); + end + + return public; + +end + + +CTR.Decipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + local updateIV = function() + iv[16] = iv[16] + 1; + if iv[16] <= 0xFF then return; end + iv[16] = AND(iv[16], 0xFF); + + iv[15] = iv[15] + 1; + if iv[15] <= 0xFF then return; end + iv[15] = AND(iv[15], 0xFF); + + iv[14] = iv[14] + 1; + if iv[14] <= 0xFF then return; end + iv[14] = AND(iv[14], 0xFF); + + iv[13] = iv[13] + 1; + if iv[13] <= 0xFF then return; end + iv[13] = AND(iv[13], 0xFF); + + iv[12] = iv[12] + 1; + if iv[12] <= 0xFF then return; end + iv[12] = AND(iv[12], 0xFF); + + iv[11] = iv[11] + 1; + if iv[11] <= 0xFF then return; end + iv[11] = AND(iv[11], 0xFF); + + iv[10] = iv[10] + 1; + if iv[10] <= 0xFF then return; end + iv[10] = AND(iv[10], 0xFF); + + iv[9] = iv[9] + 1; + if iv[9] <= 0xFF then return; end + iv[9] = AND(iv[9], 0xFF); + + return; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = iv; + out = blockCipher.encrypt(key, out); + + out = Array.XOR(out, block); + Array.writeToQueue(outputQueue, out); + updateIV(); + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + public.asString = function() + return Stream.toString(outputQueue.pop); + end + + return public; + +end + +return CTR; diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ecb.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ecb.lua new file mode 100644 index 0000000..d0f8618 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ecb.lua @@ -0,0 +1,156 @@ +local Array = require(".crypto.util.array"); +local Stream = require(".crypto.util.stream"); +local Queue = require(".crypto.util.queue"); + +local ECB = {}; + +ECB.Cipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + return public; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + block = blockCipher.encrypt(key, block); + + Array.writeToQueue(outputQueue, block); + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + public.asString = function() + return Stream.toString(outputQueue.pop); + end + + return public; + +end + +ECB.Decipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + return public; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + block = blockCipher.decrypt(key, block); + + Array.writeToQueue(outputQueue, block); + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + public.asString = function() + return Stream.toString(outputQueue.pop); + end + + return public; + +end + + +return ECB; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ofb.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ofb.lua new file mode 100644 index 0000000..adaaed1 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ofb.lua @@ -0,0 +1,172 @@ +local Array = require(".crypto.util.array"); +local Stream = require(".crypto.util.stream"); +local Queue = require(".crypto.util.queue"); + +local OFB = {}; + +OFB.Cipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = iv; + out = blockCipher.encrypt(key, out); + iv = out; + out = Array.XOR(out, block); + Array.writeToQueue(outputQueue, out); + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + public.asString = function() + return Stream.toString(outputQueue.pop); + end + + return public; + +end + +OFB.Decipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = iv; + out = blockCipher.encrypt(key, out); + iv = out; + out = Array.XOR(out, block); + Array.writeToQueue(outputQueue, out); + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + public.asString = function() + return Stream.toString(outputQueue.pop); + end + + return public; + +end + + +return OFB; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/morus.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/morus.lua new file mode 100644 index 0000000..1263337 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/morus.lua @@ -0,0 +1,398 @@ +local Hex = require(".crypto.util.hex"); + +local function state_update(s, m0, m1, m2, m3) + local s00, s01, s02, s03 = s[1], s[2], s[3], s[4] + local s10, s11, s12, s13 = s[5], s[6], s[7], s[8] + local s20, s21, s22, s23 = s[9], s[10], s[11], s[12] + local s30, s31, s32, s33 = s[13], s[14], s[15], s[16] + local s40, s41, s42, s43 = s[17], s[18], s[19], s[20] + local temp + + s00 = s00 ~ s30 + s01 = s01 ~ s31 + s02 = s02 ~ s32 + s03 = s03 ~ s33 + + temp = s33 + s33 = s32 + s32 = s31 + s31 = s30 + s30 = temp + + s00 = s00 ~ s10 & s20 + s01 = s01 ~ s11 & s21 + s02 = s02 ~ s12 & s22 + s03 = s03 ~ s13 & s23 + + s00 = (s00 << 13) | (s00 >> (64-13)) --n1 + s01 = (s01 << 13) | (s01 >> (64-13)) --n1 + s02 = (s02 << 13) | (s02 >> (64-13)) --n1 + s03 = (s03 << 13) | (s03 >> (64-13)) --n1 + + + s10 = s10 ~ m0 + s11 = s11 ~ m1 + s12 = s12 ~ m2 + s13 = s13 ~ m3 + + s10 = s10 ~ s40 + s11 = s11 ~ s41 + s12 = s12 ~ s42 + s13 = s13 ~ s43 + + temp = s43 + s43 = s41 + s41 = temp + + temp = s42 + s42 = s40 + s40 = temp + + s10 = s10 ~ (s20 & s30) + s11 = s11 ~ (s21 & s31) + s12 = s12 ~ (s22 & s32) + s13 = s13 ~ (s23 & s33) + + s10 = (s10 << 46) | (s10 >> (64-46)) --n2 + s11 = (s11 << 46) | (s11 >> (64-46)) --n2 + s12 = (s12 << 46) | (s12 >> (64-46)) --n2 + s13 = (s13 << 46) | (s13 >> (64-46)) --n2 + + + s20 = s20 ~ m0 + s21 = s21 ~ m1 + s22 = s22 ~ m2 + s23 = s23 ~ m3 + + s20 = s20 ~ s00 + s21 = s21 ~ s01 + s22 = s22 ~ s02 + s23 = s23 ~ s03 + + temp = s00 + s00 = s01 + s01 = s02 + s02 = s03 + s03 = temp + + s20 = s20 ~ s30 & s40 + s21 = s21 ~ s31 & s41 + s22 = s22 ~ s32 & s42 + s23 = s23 ~ s33 & s43 + + s20 = (s20 << 38) | (s20 >> (64-38)) --n3 + s21 = (s21 << 38) | (s21 >> (64-38)) --n3 + s22 = (s22 << 38) | (s22 >> (64-38)) --n3 + s23 = (s23 << 38) | (s23 >> (64-38)) --n3 + + + s30 = s30 ~ m0 + s31 = s31 ~ m1 + s32 = s32 ~ m2 + s33 = s33 ~ m3 + + s30 = s30 ~ s10 + s31 = s31 ~ s11 + s32 = s32 ~ s12 + s33 = s33 ~ s13 + + temp = s13 + s13 = s11 + s11 = temp + + temp = s12 + s12 = s10 + s10 = temp + + s30 = s30 ~ s40 & s00 + s31 = s31 ~ s41 & s01 + s32 = s32 ~ s42 & s02 + s33 = s33 ~ s43 & s03 + + s30 = (s30 << 7) | (s30 >> (64-7)) --n4 + s31 = (s31 << 7) | (s31 >> (64-7)) --n4 + s32 = (s32 << 7) | (s32 >> (64-7)) --n4 + s33 = (s33 << 7) | (s33 >> (64-7)) --n4 + + + s40 = s40 ~ m0 + s41 = s41 ~ m1 + s42 = s42 ~ m2 + s43 = s43 ~ m3 + + s40 = s40 ~ s20 + s41 = s41 ~ s21 + s42 = s42 ~ s22 + s43 = s43 ~ s23 + + temp = s23 + s23 = s22 + s22 = s21 + s21 = s20 + s20 = temp + + s40 = s40 ~ s00 & s10 + s41 = s41 ~ s01 & s11 + s42 = s42 ~ s02 & s12 + s43 = s43 ~ s03 & s13 + + s40 = (s40 << 4) | (s40 >> (64-4)) --n5 + s41 = (s41 << 4) | (s41 >> (64-4)) --n5 + s42 = (s42 << 4) | (s42 >> (64-4)) --n5 + s43 = (s43 << 4) | (s43 >> (64-4)) --n5 + + -- update the state array + s[1], s[2], s[3], s[4] = s00, s01, s02, s03 + s[5], s[6], s[7], s[8] = s10, s11, s12, s13 + s[9], s[10], s[11], s[12] = s20, s21, s22, s23 + s[13], s[14], s[15], s[16] = s30, s31, s32, s33 + s[17], s[18], s[19], s[20] = s40, s41, s42, s43 + +end--state_update() + +local function enc_aut_step(s, m0, m1, m2, m3) + -- m0 s00 s11 s20 s30 + local c0 = m0 ~ s[1] ~ s[6] ~ (s[9] & s[13]) + -- m1 s01 s12 s21 s31 + local c1 = m1 ~ s[2] ~ s[7] ~ (s[10] & s[14]) + -- m2 s02 s13 s22 s32 + local c2 = m2 ~ s[3] ~ s[8] ~ (s[11] & s[15]) + -- m3 s03 s10 s23 s33 + local c3 = m3 ~ s[4] ~ s[5] ~ (s[12] & s[16]) + state_update(s, m0, m1, m2, m3) + return c0, c1, c2, c3 +end + +local function dec_aut_step(s, c0, c1, c2, c3, blen) + -- mlen is the length of a last partial block + -- mlen is absent/nil for full blocks + -- return the decrypted block + -- + -- m0 s00 s11 s20 s30 + local m0 = c0 ~ s[1] ~ s[6] ~ (s[9] & s[13]) + -- m1 s01 s12 s21 s31 + local m1 = c1 ~ s[2] ~ s[7] ~ (s[10] & s[14]) + -- m2 s02 s13 s22 s32 + local m2 = c2 ~ s[3] ~ s[8] ~ (s[11] & s[15]) + -- m3 s03 s10 s23 s33 + local m3 = c3 ~ s[4] ~ s[5] ~ (s[12] & s[16]) + if blen then + -- partial block => must adjust (m0, ...) before + -- updating the state + local mblk = string.pack(" 0 then ad = e:sub(1, adlen) end + local i = 1 + while i <= adlen - 31 do --process full blocks + m0, m1, m2, m3 = string.unpack("> n) | (x << (64-n)) --INLINED + -- + A = (A ~ B) ~ ((A & B) << 1) -- H(A, B); + D = D ~ A; D = (D >> 8) | (D << (56)) --ROTR64(D, 8) --R0 + C = (C ~ D) ~ ((C & D) << 1) -- H(C, D); + B = B ~ C; B = (B >> 19) | (B << (45)) --ROTR64(B, 19) --R1 + A = (A ~ B) ~ ((A & B) << 1) -- H(A, B); + D = D ~ A; D = (D >> 40) | (D << (24)) --ROTR64(D, 40) --R2 + C = (C ~ D) ~ ((C & D) << 1) -- H(C, D); + B = B ~ C; B = (B >> 63) | (B << (1)) --ROTR64(B, 63) --R3 + s[a], s[b], s[c], s[d] = A, B, C, D +end + +local function F(s) + -- The full round. s is the state: u64[16] + -- + -- beware! in Lua, arrays are 1-based indexed, not 0-indexed as in C + -- Column step + G(s, 1, 5, 9, 13); + G(s, 2, 6, 10, 14); + G(s, 3, 7, 11, 15); + G(s, 4, 8, 12, 16); + -- Diagonal step + G(s, 1, 6, 11, 16); + G(s, 2, 7, 12, 13); + G(s, 3, 8, 9, 14); + G(s, 4, 5, 10, 15); +end + +local function permute(s) + -- the core permutation (four rounds) + for _ = 1, 4 do F(s) end +end + +local function pad(ins) + -- pad string ins to length 96 ("BYTES(NORX_R)") + local out + local inslen = #ins + if inslen == 95 then return ins .. '\x81' end -- last byte is 0x01 | 0x80 + -- here inslen is < 95, so must pad with 96-(inslen+2) zeros + out = ins .. '\x01' .. string.rep('\0', 94-inslen) .. '\x80' + assert(#out == 96) + return out +end + +local function absorb_block(s, ins, ini, tag) + -- the input string is the substring of 'ins' starting at position 'ini' + -- (we cannot use a char* as in C!) + s[16] = s[16] ~ tag + permute(s) + for i = 1, 12 do + s[i] = s[i] ~ string.unpack(" 0 then + while inlen >= 96 do + absorb_block(s, ins, i, tag) + inlen = inlen - 96 + i = i + 96 + end + absorb_lastblock(s, ins:sub(i), tag) + end--if +end + +local function encrypt_data(s, out_table, ins) + local inlen = #ins + local i = 1 + if inlen > 0 then + while inlen >= 96 do + encrypt_block(s, out_table, ins, i) + inlen = inlen - 96 + i = i + 96 + end + encrypt_lastblock(s, out_table, ins:sub(i)) + end +end + +local function decrypt_data(s, out_table, ins) + local inlen = #ins + local i = 1 + if inlen > 0 then + while inlen >= 96 do + decrypt_block(s, out_table, ins, i) + inlen = inlen - 96 + i = i + 96 + end + decrypt_lastblock(s, out_table, ins:sub(i)) + end +end + +local function finalize(s, k) + -- return the authentication tag (32-byte string) + -- + s[16] = s[16] ~ FINAL_TAG + permute(s) + -- + local k1, k2, k3, k4 = string.unpack("= 32) + local out_table = {} + local state = init(key, nonce) + absorb_data(state, header, HEADER_TAG) + local ctag = crypted:sub(#crypted - 32 + 1) + local c = crypted:sub(1, #crypted - 32) + decrypt_data(state, out_table, c) + absorb_data(state, trailer, TRAILER_TAG) + local tag = finalize(state, key) + if not verify_tag(tag, ctag) then return nil, "auth failure" end + local plain = table.concat(out_table) + return plain +end + +return { + encrypt = aead_encrypt, + decrypt = aead_decrypt, + -- + key_size = 32, + nonce_size = 32, + variant = "NORX 64-4-1", +} diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/blake2b.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/blake2b.lua new file mode 100644 index 0000000..0b68940 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/blake2b.lua @@ -0,0 +1,190 @@ +local Hex = require(".crypto.util.hex"); + + +local function ROTR64(x, n) + return (x >> n) | (x << (64-n)) +end + +-- G Mixing function. + +local function G(v, a, b, c, d, x, y) + v[a] = v[a] + v[b] + x + v[d] = ROTR64(v[d] ~ v[a], 32) + v[c] = v[c] + v[d] + v[b] = ROTR64(v[b] ~ v[c], 24) + v[a] = v[a] + v[b] + y + v[d] = ROTR64(v[d] ~ v[a], 16) + v[c] = v[c] + v[d] + v[b] = ROTR64(v[b] ~ v[c], 63) +end + +-- Initialization Vector. +local iv = { + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179 +} + +local sigma = { + -- array index start at 1 in Lua, + -- => all the permutation values are incremented by one + { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, + { 15, 11, 5, 9, 10, 16, 14, 7, 2, 13, 1, 3, 12, 8, 6, 4 }, + { 12, 9, 13, 1, 6, 3, 16, 14, 11, 15, 4, 7, 8, 2, 10, 5 }, + { 8, 10, 4, 2, 14, 13, 12, 15, 3, 7, 6, 11, 5, 1, 16, 9 }, + { 10, 1, 6, 8, 3, 5, 11, 16, 15, 2, 12, 13, 7, 9, 4, 14 }, + { 3, 13, 7, 11, 1, 12, 9, 4, 5, 14, 8, 6, 16, 15, 2, 10 }, + { 13, 6, 2, 16, 15, 14, 5, 11, 1, 8, 7, 4, 10, 3, 9, 12 }, + { 14, 12, 8, 15, 13, 2, 4, 10, 6, 1, 16, 5, 9, 7, 3, 11 }, + { 7, 16, 15, 10, 12, 4, 1, 9, 13, 3, 14, 8, 2, 5, 11, 6 }, + { 11, 3, 9, 5, 8, 7, 2, 6, 16, 12, 10, 15, 4, 13, 14, 1 }, + { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, + { 15, 11, 5, 9, 10, 16, 14, 7, 2, 13, 1, 3, 12, 8, 6, 4 } +} + + +local function compress(ctx, last) + -- Compression function. "last" flag indicates last block. + local v, m = {}, {} -- both v and m are u64[16] + for i = 1, 8 do + v[i] = ctx.h[i] + v[i+8] = iv[i] + end + v[13] = v[13] ~ ctx.t[1] -- low 64 bits of offset + v[14] = v[14] ~ ctx.t[2] -- high 64 bits + if last then v[15] = ~v[15] end + for i = 0, 15 do -- get little-endian words + m[i+1] = string.unpack(" 64 or (key and #key > 64) then + return nil, "illegal parameters" + end + local ctx = {h={}, t={}, c=1, outlen=outlen} -- the blake2 context + -- note: ctx.c is the index of 1st byte free in input buffer (ctx.b) + -- it is not used in this implementation + for i = 1, 8 do ctx.h[i] = iv[i] end -- state, "param block" + ctx.h[1] = ctx.h[1] ~ 0x01010000 ~ (keylen << 8) ~ outlen + ctx.t[1] = 0 --input count low word + ctx.t[2] = 0 --input count high word + -- zero input block + ctx.b = "" + if keylen > 0 then + update(ctx, key) + -- ctx.c = 128 -- pad b with zero bytes + ctx.b = ctx.b .. string.rep('\0', 128 - #ctx.b) + assert(#ctx.b == 128) + end + return ctx +end + +update = function(ctx, data) + -- buffer mgt cannot be done the C way.. + local bln, rln, iln + local i = 1 -- index of 1st byte to process in data + while true do + bln = #ctx.b -- current number of bytes in the input buffer + assert(bln <= 128) + if bln == 128 then --ctx.b is full; process it. + -- add counters + ctx.t[1] = ctx.t[1] + 128 + -- warning: this is a signed 64bit addition + -- here it is assumed that the total input is less + -- than 2^63 bytes (this should be enough for a + -- pure Lua implementation!) => ctx.t[1] overflow is ignored. + compress(ctx, false) -- false means not last + ctx.b = "" -- empty buffer + else -- ctx.b is not full; append more bytes from data + rln = 128 - bln -- remaining space (in bytes) in ctx.b + iln = #data - i + 1 -- number of bytes yet to process in data + if iln < rln then + ctx.b = ctx.b .. data:sub(i, i + iln -1) + -- here, all data bytes have been processed or put in + -- buffer and buffer is not full. we are done. + break + else + ctx.b = ctx.b .. data:sub(i, i + rln -1) + i = i + rln + end + end + end +end + +local function final(ctx) + -- finalize the hash and return the digest as a string + -- + local bln = #ctx.b + -- add number of remaining bytes in buffer (ignore carry overflow) + ctx.t[1] = ctx.t[1] + bln + -- pad the buffer with zero bytes + local rln = 128 - bln -- remaining space (in bytes) in ctx.b + ctx.b = ctx.b .. string.rep('\0', rln) + compress(ctx, true) -- true means final block + -- extract the digest (outlen bytes long) + local outtbl = {} + for i = 0, ctx.outlen - 1 do + outtbl[i+1] = string.char( + (ctx.h[(i >> 3) + 1] >> (8 * (i & 7))) & 0xff) + end + local dig = table.concat(outtbl) + return outtbl +end + +--- Calculates the BLAKE2b hash of the given data. +--- @param data string - The input data to be hashed. +--- @param outlen? number (optional) - The desired length of the hash output. Defaults to 64. +--- @param key? string (optional) - The key to be used for the hash calculation. Defaults to an empty string. +--- @returns table - A table containing the hash in bytes, string, and hex formats. +local function black2b(data, outlen, key) + local ctx, msg = init(outlen, key) + if not ctx then return ctx, msg end + update(ctx, data) + local bytes = final(ctx) + local hash = table.concat(bytes) + + local public = {} + + public.asBytes = function() + return bytes + end + + public.asString = function() + return hash + end + + public.asHex = function() + return Hex.stringToHex(hash) + end + + return public +end + +return black2b diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/init.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/init.lua new file mode 100644 index 0000000..0646c29 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/init.lua @@ -0,0 +1,29 @@ +local MD2 = require(".crypto.digest.md2") +local MD4 = require(".crypto.digest.md4") +local MD5 = require(".crypto.digest.md5") +local SHA1 = require(".crypto.digest.sha1") +local SHA2_256 = require(".crypto.digest.sha2_256") +local SHA2_512 = require(".crypto.digest.sha2_512") +local SHA3 = require(".crypto.digest.sha3") +local Blake2b = require(".crypto.digest.blake2b") + + +local digest = { + _version = "0.0.1", + md2 = MD2, + md4 = MD4, + md5 = MD5, + sha1 = SHA1.sha1, + sha2_256 = SHA2_256.sha2_256, + sha2_512 = SHA2_512, + sha3_256 = SHA3.sha3_256, + sha3_512 = SHA3.sha3_512, + keccak256 = SHA3.keccak256, + keccak512 = SHA3.keccak512, + blake2b = Blake2b +} + + + + +return digest diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md2.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md2.lua new file mode 100644 index 0000000..87fede0 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md2.lua @@ -0,0 +1,112 @@ +local Bit = require(".crypto.util.bit"); +local Queue = require(".crypto.util.queue"); + +local SUBST = { + 0x29, 0x2E, 0x43, 0xC9, 0xA2, 0xD8, 0x7C, 0x01, 0x3D, 0x36, 0x54, 0xA1, 0xEC, 0xF0, 0x06, 0x13, + 0x62, 0xA7, 0x05, 0xF3, 0xC0, 0xC7, 0x73, 0x8C, 0x98, 0x93, 0x2B, 0xD9, 0xBC, 0x4C, 0x82, 0xCA, + 0x1E, 0x9B, 0x57, 0x3C, 0xFD, 0xD4, 0xE0, 0x16, 0x67, 0x42, 0x6F, 0x18, 0x8A, 0x17, 0xE5, 0x12, + 0xBE, 0x4E, 0xC4, 0xD6, 0xDA, 0x9E, 0xDE, 0x49, 0xA0, 0xFB, 0xF5, 0x8E, 0xBB, 0x2F, 0xEE, 0x7A, + 0xA9, 0x68, 0x79, 0x91, 0x15, 0xB2, 0x07, 0x3F, 0x94, 0xC2, 0x10, 0x89, 0x0B, 0x22, 0x5F, 0x21, + 0x80, 0x7F, 0x5D, 0x9A, 0x5A, 0x90, 0x32, 0x27, 0x35, 0x3E, 0xCC, 0xE7, 0xBF, 0xF7, 0x97, 0x03, + 0xFF, 0x19, 0x30, 0xB3, 0x48, 0xA5, 0xB5, 0xD1, 0xD7, 0x5E, 0x92, 0x2A, 0xAC, 0x56, 0xAA, 0xC6, + 0x4F, 0xB8, 0x38, 0xD2, 0x96, 0xA4, 0x7D, 0xB6, 0x76, 0xFC, 0x6B, 0xE2, 0x9C, 0x74, 0x04, 0xF1, + 0x45, 0x9D, 0x70, 0x59, 0x64, 0x71, 0x87, 0x20, 0x86, 0x5B, 0xCF, 0x65, 0xE6, 0x2D, 0xA8, 0x02, + 0x1B, 0x60, 0x25, 0xAD, 0xAE, 0xB0, 0xB9, 0xF6, 0x1C, 0x46, 0x61, 0x69, 0x34, 0x40, 0x7E, 0x0F, + 0x55, 0x47, 0xA3, 0x23, 0xDD, 0x51, 0xAF, 0x3A, 0xC3, 0x5C, 0xF9, 0xCE, 0xBA, 0xC5, 0xEA, 0x26, + 0x2C, 0x53, 0x0D, 0x6E, 0x85, 0x28, 0x84, 0x09, 0xD3, 0xDF, 0xCD, 0xF4, 0x41, 0x81, 0x4D, 0x52, + 0x6A, 0xDC, 0x37, 0xC8, 0x6C, 0xC1, 0xAB, 0xFA, 0x24, 0xE1, 0x7B, 0x08, 0x0C, 0xBD, 0xB1, 0x4A, + 0x78, 0x88, 0x95, 0x8B, 0xE3, 0x63, 0xE8, 0x6D, 0xE9, 0xCB, 0xD5, 0xFE, 0x3B, 0x00, 0x1D, 0x39, + 0xF2, 0xEF, 0xB7, 0x0E, 0x66, 0x58, 0xD0, 0xE4, 0xA6, 0x77, 0x72, 0xF8, 0xEB, 0x75, 0x4B, 0x0A, + 0x31, 0x44, 0x50, 0xB4, 0x8F, 0xED, 0x1F, 0x1A, 0xDB, 0x99, 0x8D, 0x33, 0x9F, 0x11, 0x83, 0x14 }; + +local XOR = Bit.bxor; + +local MD2 = function(stream) + local queue = Queue(); + local public = {} + + local X = {}; + for i = 0, 47 do + X[i] = 0x00; + end + + local C = {}; + for i = 0, 15 do + C[i] = 0x00; + end + + local processBlock = function() + local block = {}; + + for i = 0, 15 do + block[i] = queue.pop(); + end + + for i = 0, 15 do + X[i + 16] = block[i]; + X[i + 32] = XOR(X[i], block[i]); --mix + end + + local t; + + --update block + t = 0; + for i = 0, 17 do + for j = 0, 47 do + X[j] = XOR(X[j], SUBST[t + 1]); + t = X[j]; + end + t = (t + i) % 256; + end + + --update checksum + t = C[15]; + for i = 0, 15 do + C[i] = XOR(C[i], SUBST[XOR(block[i], t) + 1]); + t = C[i]; + end + + end + + for b in stream do + queue.push(b); + if(queue.size() >= 16) then processBlock(); end + end + + local i = 16 - queue.size(); + + while queue.size() < 16 do + queue.push(i); + end + + processBlock(); + + queue.push(C[ 0]); queue.push(C[ 1]); queue.push(C[ 2]); queue.push(C[ 3]); + queue.push(C[ 4]); queue.push(C[ 5]); queue.push(C[ 6]); queue.push(C[ 7]); + queue.push(C[ 8]); queue.push(C[ 9]); queue.push(C[10]); queue.push(C[11]); + queue.push(C[12]); queue.push(C[13]); queue.push(C[14]); queue.push(C[15]); + + processBlock(); + + public.asBytes = function() + return {X[ 0], X[ 1], X[ 2], X[ 3], X[ 4], X[ 5], X[ 6], X[ 7], + X[ 8], X[ 9], X[10], X[11], X[12], X[13], X[14], X[15]}; + end + + public.asHex = function() + return string.format("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + X[ 0], X[ 1], X[ 2], X[ 3], X[ 4], X[ 5], X[ 6], X[ 7], + X[ 8], X[ 9], X[10], X[11], X[12], X[13], X[14], X[15]); + end + + public.asString = function() + return string.pack(string.rep('B', 16), + X[ 0], X[ 1], X[ 2], X[ 3], X[ 4], X[ 5], X[ 6], X[ 7], + X[ 8], X[ 9], X[10], X[11], X[12], X[13], X[14], X[15]); + end + + return public; + +end + +return MD2; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md4.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md4.lua new file mode 100644 index 0000000..eee89a7 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md4.lua @@ -0,0 +1,193 @@ +local Bit = require(".crypto.util.bit"); +local Queue = require(".crypto.util.queue"); + +local AND = Bit.band; +local OR = Bit.bor; +local NOT = Bit.bnot; +local XOR = Bit.bxor; +local LROT = Bit.lrotate; +local LSHIFT = Bit.lshift; +local RSHIFT = Bit.rshift; + +--MD4 is little-endian +local bytes2word = function(b0, b1, b2, b3) + local i = b3; i = LSHIFT(i, 8); + i = OR(i, b2); i = LSHIFT(i, 8); + i = OR(i, b1); i = LSHIFT(i, 8); + i = OR(i, b0); + return i; +end + +local word2bytes = function(word) + local b0, b1, b2, b3; + b0 = AND(word, 0xFF); word = RSHIFT(word, 8); + b1 = AND(word, 0xFF); word = RSHIFT(word, 8); + b2 = AND(word, 0xFF); word = RSHIFT(word, 8); + b3 = AND(word, 0xFF); + return b0, b1, b2, b3; +end + +local dword2bytes = function(i) + local b4, b5, b6, b7 = word2bytes(math.floor(i / 0x100000000)); + local b0, b1, b2, b3 = word2bytes(i); + return b0, b1, b2, b3, b4, b5, b6, b7; +end + +local F = function(x, y, z) return OR(AND(x, y), AND(NOT(x), z)); end +local G = function(x, y, z) return OR(AND(x, y), OR(AND(x, z), AND(y, z))); end +local H = function(x, y, z) return XOR(x, XOR(y, z)); end + + +local MD4 = function(stream) + + local queue = Queue(); + + local A = 0x67452301; + local B = 0xefcdab89; + local C = 0x98badcfe; + local D = 0x10325476; + local public = {}; + + local processBlock = function() + local a = A; + local b = B; + local c = C; + local d = D; + + local X = {}; + + for i = 0, 15 do + X[i] = bytes2word(queue.pop(), queue.pop(), queue.pop(), queue.pop()); + end + + a = LROT(a + F(b, c, d) + X[0], 3); + d = LROT(d + F(a, b, c) + X[1], 7); + c = LROT(c + F(d, a, b) + X[2], 11); + b = LROT(b + F(c, d, a) + X[3], 19); + + a = LROT(a + F(b, c, d) + X[4], 3); + d = LROT(d + F(a, b, c) + X[5], 7); + c = LROT(c + F(d, a, b) + X[6], 11); + b = LROT(b + F(c, d, a) + X[7], 19); + + a = LROT(a + F(b, c, d) + X[8], 3); + d = LROT(d + F(a, b, c) + X[9], 7); + c = LROT(c + F(d, a, b) + X[10], 11); + b = LROT(b + F(c, d, a) + X[11], 19); + + a = LROT(a + F(b, c, d) + X[12], 3); + d = LROT(d + F(a, b, c) + X[13], 7); + c = LROT(c + F(d, a, b) + X[14], 11); + b = LROT(b + F(c, d, a) + X[15], 19); + + + a = LROT(a + G(b, c, d) + X[0] + 0x5A827999, 3); + d = LROT(d + G(a, b, c) + X[4] + 0x5A827999, 5); + c = LROT(c + G(d, a, b) + X[8] + 0x5A827999, 9); + b = LROT(b + G(c, d, a) + X[12] + 0x5A827999, 13); + + a = LROT(a + G(b, c, d) + X[1] + 0x5A827999, 3); + d = LROT(d + G(a, b, c) + X[5] + 0x5A827999, 5); + c = LROT(c + G(d, a, b) + X[9] + 0x5A827999, 9); + b = LROT(b + G(c, d, a) + X[13] + 0x5A827999, 13); + + a = LROT(a + G(b, c, d) + X[2] + 0x5A827999, 3); + d = LROT(d + G(a, b, c) + X[6] + 0x5A827999, 5); + c = LROT(c + G(d, a, b) + X[10] + 0x5A827999, 9); + b = LROT(b + G(c, d, a) + X[14] + 0x5A827999, 13); + + a = LROT(a + G(b, c, d) + X[3] + 0x5A827999, 3); + d = LROT(d + G(a, b, c) + X[7] + 0x5A827999, 5); + c = LROT(c + G(d, a, b) + X[11] + 0x5A827999, 9); + b = LROT(b + G(c, d, a) + X[15] + 0x5A827999, 13); + + + a = LROT(a + H(b, c, d) + X[0] + 0x6ED9EBA1, 3); + d = LROT(d + H(a, b, c) + X[8] + 0x6ED9EBA1, 9); + c = LROT(c + H(d, a, b) + X[4] + 0x6ED9EBA1, 11); + b = LROT(b + H(c, d, a) + X[12] + 0x6ED9EBA1, 15); + + a = LROT(a + H(b, c, d) + X[2] + 0x6ED9EBA1, 3); + d = LROT(d + H(a, b, c) + X[10] + 0x6ED9EBA1, 9); + c = LROT(c + H(d, a, b) + X[6] + 0x6ED9EBA1, 11); + b = LROT(b + H(c, d, a) + X[14] + 0x6ED9EBA1, 15); + + a = LROT(a + H(b, c, d) + X[1] + 0x6ED9EBA1, 3); + d = LROT(d + H(a, b, c) + X[9] + 0x6ED9EBA1, 9); + c = LROT(c + H(d, a, b) + X[5] + 0x6ED9EBA1, 11); + b = LROT(b + H(c, d, a) + X[13] + 0x6ED9EBA1, 15); + + a = LROT(a + H(b, c, d) + X[3] + 0x6ED9EBA1, 3); + d = LROT(d + H(a, b, c) + X[11] + 0x6ED9EBA1, 9); + c = LROT(c + H(d, a, b) + X[7] + 0x6ED9EBA1, 11); + b = LROT(b + H(c, d, a) + X[15] + 0x6ED9EBA1, 15); + + + A = AND(A + a, 0xFFFFFFFF); + B = AND(B + b, 0xFFFFFFFF); + C = AND(C + c, 0xFFFFFFFF); + D = AND(D + d, 0xFFFFFFFF); + end + + + for s in stream do + queue.push(s); + if (queue.size() >= 64) then processBlock(); end + end + + local bits = queue.getHead() * 8; + + queue.push(0x80); + while ((queue.size() + 7) % 64) < 63 do + queue.push(0x00); + end + + local b0, b1, b2, b3, b4, b5, b6, b7 = dword2bytes(bits); + + queue.push(b0); + queue.push(b1); + queue.push(b2); + queue.push(b3); + queue.push(b4); + queue.push(b5); + queue.push(b6); + queue.push(b7); + + while queue.size() > 0 do + processBlock(); + end + + public.asBytes = function() + local b0, b1, b2, b3 = word2bytes(A); + local b4, b5, b6, b7 = word2bytes(B); + local b8, b9, b10, b11 = word2bytes(C); + local b12, b13, b14, b15 = word2bytes(D); + + return {b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15}; + end + + public.asHex = function() + local b0, b1, b2, b3 = word2bytes(A); + local b4, b5, b6, b7 = word2bytes(B); + local b8, b9, b10, b11 = word2bytes(C); + local b12, b13, b14, b15 = word2bytes(D); + + return string.format("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15); + end + + public.asString = function() + local b0, b1, b2, b3 = word2bytes(A); + local b4, b5, b6, b7 = word2bytes(B); + local b8, b9, b10, b11 = word2bytes(C); + local b12, b13, b14, b15 = word2bytes(D); + + return string.pack(string.rep('B', 16), + b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15); + end + + return public; + +end + +return MD4; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md5.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md5.lua new file mode 100644 index 0000000..be40518 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md5.lua @@ -0,0 +1,178 @@ +local Bit = require(".crypto.util.bit"); +local Queue = require(".crypto.util.queue"); + +local SHIFT = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; + +local CONSTANTS = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; + +local AND = Bit.band; +local OR = Bit.bor; +local NOT = Bit.bnot; +local XOR = Bit.bxor; +local LROT = Bit.lrotate; +local LSHIFT = Bit.lshift; +local RSHIFT = Bit.rshift; + +--MD5 is little-endian +local bytes2word = function(b0, b1, b2, b3) + local i = b3; i = LSHIFT(i, 8); + i = OR(i, b2); i = LSHIFT(i, 8); + i = OR(i, b1); i = LSHIFT(i, 8); + i = OR(i, b0); + return i; +end + +local word2bytes = function(word) + local b0, b1, b2, b3; + b0 = AND(word, 0xFF); word = RSHIFT(word, 8); + b1 = AND(word, 0xFF); word = RSHIFT(word, 8); + b2 = AND(word, 0xFF); word = RSHIFT(word, 8); + b3 = AND(word, 0xFF); + return b0, b1, b2, b3; +end + +local dword2bytes = function(i) + local b4, b5, b6, b7 = word2bytes(math.floor(i / 0x100000000)); + local b0, b1, b2, b3 = word2bytes(i); + return b0, b1, b2, b3, b4, b5, b6, b7; +end + +local F = function(x, y, z) return OR(AND(x, y), AND(NOT(x), z)); end +local G = function(x, y, z) return OR(AND(x, z), AND(y, NOT(z))); end +local H = function(x, y, z) return XOR(x, XOR(y, z)); end +local I = function(x, y, z) return XOR(y, OR(x, NOT(z))); end + +local MD5 = function(stream) + + local queue = Queue(); + + local A = 0x67452301; + local B = 0xefcdab89; + local C = 0x98badcfe; + local D = 0x10325476; + local public = {}; + + local processBlock = function() + local a = A; + local b = B; + local c = C; + local d = D; + + local X = {}; + + for i = 1, 16 do + X[i] = bytes2word(queue.pop(), queue.pop(), queue.pop(), queue.pop()); + end + + for i = 0, 63 do + local f, g, temp; + + if (0 <= i) and (i <= 15) then + f = F(b, c, d); + g = i; + elseif (16 <= i) and (i <= 31) then + f = G(b, c, d); + g = (5 * i + 1) % 16; + elseif (32 <= i) and (i <= 47) then + f = H(b, c, d); + g = (3 * i + 5) % 16; + elseif (48 <= i) and (i <= 63) then + f = I(b, c, d); + g = (7 * i) % 16; + end + temp = d; + d = c; + c = b; + b = b + LROT((a + f + CONSTANTS[i + 1] + X[g + 1]), SHIFT[i + 1]); + a = temp; + end + + A = AND(A + a, 0xFFFFFFFF); + B = AND(B + b, 0xFFFFFFFF); + C = AND(C + c, 0xFFFFFFFF); + D = AND(D + d, 0xFFFFFFFF); + end + + for s in stream do + queue.push(s); + if (queue.size() >= 64) then processBlock(); end + end + + local bits = queue.getHead() * 8; + + queue.push(0x80); + while ((queue.size() + 7) % 64) < 63 do + queue.push(0x00); + end + + local b0, b1, b2, b3, b4, b5, b6, b7 = dword2bytes(bits); + + queue.push(b0); + queue.push(b1); + queue.push(b2); + queue.push(b3); + queue.push(b4); + queue.push(b5); + queue.push(b6); + queue.push(b7); + + while queue.size() > 0 do + processBlock(); + end + + public.asBytes = function() + local b0, b1, b2, b3 = word2bytes(A); + local b4, b5, b6, b7 = word2bytes(B); + local b8, b9, b10, b11 = word2bytes(C); + local b12, b13, b14, b15 = word2bytes(D); + + return {b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15}; + end + + public.asHex = function() + local b0, b1, b2, b3 = word2bytes(A); + local b4, b5, b6, b7 = word2bytes(B); + local b8, b9, b10, b11 = word2bytes(C); + local b12, b13, b14, b15 = word2bytes(D); + + return string.format("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15); + end + + public.asString = function() + local b0, b1, b2, b3 = word2bytes(A); + local b4, b5, b6, b7 = word2bytes(B); + local b8, b9, b10, b11 = word2bytes(C); + local b12, b13, b14, b15 = word2bytes(D); + + return string.pack(string.rep('B', 16), + b0, b1, b2, b3, b4, b5, b6, b7, b8, + b9, b10, b11, b12, b13, b14, b15 + ) + end + + return public; + +end + +return MD5; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha1.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha1.lua new file mode 100644 index 0000000..589acf8 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha1.lua @@ -0,0 +1,191 @@ +local Bit = require(".crypto.util.bit"); +local Queue = require(".crypto.util.queue"); + +local AND = Bit.band; +local OR = Bit.bor; +local XOR = Bit.bxor; +local LROT = Bit.lrotate; +local LSHIFT = Bit.lshift; +local RSHIFT = Bit.rshift; + +--SHA1 is big-endian +local bytes2word = function(b0, b1, b2, b3) + local i = b0; i = LSHIFT(i, 8); + i = OR(i, b1); i = LSHIFT(i, 8); + i = OR(i, b2); i = LSHIFT(i, 8); + i = OR(i, b3); + return i; +end + +local word2bytes = function(word) + local b0, b1, b2, b3; + b3 = AND(word, 0xFF); word = RSHIFT(word, 8); + b2 = AND(word, 0xFF); word = RSHIFT(word, 8); + b1 = AND(word, 0xFF); word = RSHIFT(word, 8); + b0 = AND(word, 0xFF); + return b0, b1, b2, b3; +end + +local dword2bytes = function(i) + local b4, b5, b6, b7 = word2bytes(i); + local b0, b1, b2, b3 = word2bytes(math.floor(i / 0x100000000)); + return b0, b1, b2, b3, b4, b5, b6, b7; +end + +local F = function(x, y, z) return XOR(z, AND(x, XOR(y, z))); end +local G = function(x, y, z) return XOR(x, XOR(y, z)); end +local H = function(x, y, z) return OR(AND(x, OR(y, z)), AND(y, z)); end +local I = function(x, y, z) return XOR(x, XOR(y, z)); end + +local SHA1 = function() + + local queue = Queue(); + + local h0 = 0x67452301; + local h1 = 0xEFCDAB89; + local h2 = 0x98BADCFE; + local h3 = 0x10325476; + local h4 = 0xC3D2E1F0; + + local public = {}; + + local processBlock = function() + local a = h0; + local b = h1; + local c = h2; + local d = h3; + local e = h4; + local temp; + local k; + + local w = {}; + for i = 0, 15 do + w[i] = bytes2word(queue.pop(), queue.pop(), queue.pop(), queue.pop()); + end + + for i = 16, 79 do + w[i] = LROT((XOR(XOR(w[i - 3], w[i - 8]), XOR(w[i - 14], w[i - 16]))), 1); + end + + for i = 0, 79 do + if (i <= 19) then + temp = F(b, c, d); + k = 0x5A827999; + elseif (i <= 39) then + temp = G(b, c, d); + k = 0x6ED9EBA1; + elseif (i <= 59) then + temp = H(b, c, d); + k = 0x8F1BBCDC; + else + temp = I(b, c, d); + k = 0xCA62C1D6; + end + temp = LROT(a, 5) + temp + e + k + w[i]; + e = d; + d = c; + c = LROT(b, 30); + b = a; + a = temp; + end + + h0 = AND(h0 + a, 0xFFFFFFFF); + h1 = AND(h1 + b, 0xFFFFFFFF); + h2 = AND(h2 + c, 0xFFFFFFFF); + h3 = AND(h3 + d, 0xFFFFFFFF); + h4 = AND(h4 + e, 0xFFFFFFFF); + end + + public.init = function() + queue.reset(); + h0 = 0x67452301; + h1 = 0xEFCDAB89; + h2 = 0x98BADCFE; + h3 = 0x10325476; + h4 = 0xC3D2E1F0; + return public; + end + + + public.update = function(bytes) + for b in bytes do + queue.push(b); + if queue.size() >= 64 then processBlock(); end + end + + return public; + end + + public.finish = function() + local bits = queue.getHead() * 8; + + queue.push(0x80); + while ((queue.size() + 7) % 64) < 63 do + queue.push(0x00); + end + + local b0, b1, b2, b3, b4, b5, b6, b7 = dword2bytes(bits); + + queue.push(b0); + queue.push(b1); + queue.push(b2); + queue.push(b3); + queue.push(b4); + queue.push(b5); + queue.push(b6); + queue.push(b7); + + while queue.size() > 0 do + processBlock(); + end + + return public; + end + + public.asBytes = function() + local b0, b1, b2, b3 = word2bytes(h0); + local b4, b5, b6, b7 = word2bytes(h1); + local b8, b9, b10, b11 = word2bytes(h2); + local b12, b13, b14, b15 = word2bytes(h3); + local b16, b17, b18, b19 = word2bytes(h4); + + return {b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19}; + end + + public.asHex = function() + local b0, b1, b2, b3 = word2bytes(h0); + local b4, b5, b6, b7 = word2bytes(h1); + local b8, b9, b10, b11 = word2bytes(h2); + local b12, b13, b14, b15 = word2bytes(h3); + local b16, b17, b18, b19 = word2bytes(h4); + + return string.format("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19); + end + + public.asString = function() + local b0, b1, b2, b3 = word2bytes(h0); + local b4, b5, b6, b7 = word2bytes(h1); + local b8, b9, b10, b11 = word2bytes(h2); + local b12, b13, b14, b15 = word2bytes(h3); + local b16, b17, b18, b19 = word2bytes(h4); + + return string.pack(string.rep('B', 20), + b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19); + end + + return public; +end + + +local sha1 = function(stream) + local result = SHA1() + .update(stream) + .finish() + return result +end + +return { + sha1 = sha1, + SHA1 = SHA1 +} \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_256.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_256.lua new file mode 100644 index 0000000..64be951 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_256.lua @@ -0,0 +1,230 @@ +local Bit = require(".crypto.util.bit"); +local Queue = require(".crypto.util.queue"); +local Stream = require(".crypto.util.stream"); + +local CONSTANTS = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 }; + +local fmt = "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" .. + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" + +local AND = Bit.band; +local OR = Bit.bor; +local NOT = Bit.bnot; +local XOR = Bit.bxor; +local RROT = Bit.rrotate; +local LSHIFT = Bit.lshift; +local RSHIFT = Bit.rshift; + +--SHA2 is big-endian +local bytes2word = function(b0, b1, b2, b3) + local i = b0; i = LSHIFT(i, 8); + i = OR(i, b1); i = LSHIFT(i, 8); + i = OR(i, b2); i = LSHIFT(i, 8); + i = OR(i, b3); + return i; +end + +local word2bytes = function(word) + local b0, b1, b2, b3; + b3 = AND(word, 0xFF); word = RSHIFT(word, 8); + b2 = AND(word, 0xFF); word = RSHIFT(word, 8); + b1 = AND(word, 0xFF); word = RSHIFT(word, 8); + b0 = AND(word, 0xFF); + return b0, b1, b2, b3; +end + +local dword2bytes = function(i) + local b4, b5, b6, b7 = word2bytes(i); + local b0, b1, b2, b3 = word2bytes(math.floor(i / 0x100000000)); + return b0, b1, b2, b3, b4, b5, b6, b7; +end + + +local SHA2_256 = function() + + local queue = Queue(); + + local h0 = 0x6a09e667; + local h1 = 0xbb67ae85; + local h2 = 0x3c6ef372; + local h3 = 0xa54ff53a; + local h4 = 0x510e527f; + local h5 = 0x9b05688c; + local h6 = 0x1f83d9ab; + local h7 = 0x5be0cd19; + + local public = {}; + + local processBlock = function() + local a = h0; + local b = h1; + local c = h2; + local d = h3; + local e = h4; + local f = h5; + local g = h6; + local h = h7; + + local w = {}; + + for i = 0, 15 do + w[i] = bytes2word(queue.pop(), queue.pop(), queue.pop(), queue.pop()); + end + + for i = 16, 63 do + local s0 = XOR(RROT(w[i - 15], 7), XOR(RROT(w[i - 15], 18), RSHIFT(w[i - 15], 3))); + local s1 = XOR(RROT(w[i - 2], 17), XOR(RROT(w[i - 2], 19), RSHIFT(w[i - 2], 10))); + w[i] = AND(w[i - 16] + s0 + w[i - 7] + s1, 0xFFFFFFFF); + end + + for i = 0, 63 do + local s1 = XOR(RROT(e, 6), XOR(RROT(e, 11), RROT(e, 25))); + local ch = XOR(AND(e, f), AND(NOT(e), g)); + local temp1 = h + s1 + ch + CONSTANTS[i + 1] + w[i]; + local s0 = XOR(RROT(a, 2), XOR(RROT(a, 13), RROT(a, 22))); + local maj = XOR(AND(a, b), XOR(AND(a, c), AND(b, c))); + local temp2 = s0 + maj; + + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + end + + h0 = AND(h0 + a, 0xFFFFFFFF); + h1 = AND(h1 + b, 0xFFFFFFFF); + h2 = AND(h2 + c, 0xFFFFFFFF); + h3 = AND(h3 + d, 0xFFFFFFFF); + h4 = AND(h4 + e, 0xFFFFFFFF); + h5 = AND(h5 + f, 0xFFFFFFFF); + h6 = AND(h6 + g, 0xFFFFFFFF); + h7 = AND(h7 + h, 0xFFFFFFFF); + end + + public.init = function() + queue.reset(); + + h0 = 0x6a09e667; + h1 = 0xbb67ae85; + h2 = 0x3c6ef372; + h3 = 0xa54ff53a; + h4 = 0x510e527f; + h5 = 0x9b05688c; + h6 = 0x1f83d9ab; + h7 = 0x5be0cd19; + + return public; + end + + public.update = function(bytes) + for b in bytes do + queue.push(b); + if queue.size() >= 64 then processBlock(); end + end + + return public; + end + + public.finish = function() + local bits = queue.getHead() * 8; + + queue.push(0x80); + while ((queue.size() + 7) % 64) < 63 do + queue.push(0x00); + end + + local b0, b1, b2, b3, b4, b5, b6, b7 = dword2bytes(bits); + + queue.push(b0); + queue.push(b1); + queue.push(b2); + queue.push(b3); + queue.push(b4); + queue.push(b5); + queue.push(b6); + queue.push(b7); + + while queue.size() > 0 do + processBlock(); + end + + return public; + end + + public.asBytes = function() + local b0, b1, b2, b3 = word2bytes(h0); + local b4, b5, b6, b7 = word2bytes(h1); + local b8, b9, b10, b11 = word2bytes(h2); + local b12, b13, b14, b15 = word2bytes(h3); + local b16, b17, b18, b19 = word2bytes(h4); + local b20, b21, b22, b23 = word2bytes(h5); + local b24, b25, b26, b27 = word2bytes(h6); + local b28, b29, b30, b31 = word2bytes(h7); + + + return { b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 + , b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31}; + end + + public.asHex = function() + local b0, b1, b2, b3 = word2bytes(h0); + local b4, b5, b6, b7 = word2bytes(h1); + local b8, b9, b10, b11 = word2bytes(h2); + local b12, b13, b14, b15 = word2bytes(h3); + local b16, b17, b18, b19 = word2bytes(h4); + local b20, b21, b22, b23 = word2bytes(h5); + local b24, b25, b26, b27 = word2bytes(h6); + local b28, b29, b30, b31 = word2bytes(h7); + + return string.format(fmt, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 + , b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31); + end + + public.asString = function() + local b0, b1, b2, b3 = word2bytes(h0); + local b4, b5, b6, b7 = word2bytes(h1); + local b8, b9, b10, b11 = word2bytes(h2); + local b12, b13, b14, b15 = word2bytes(h3); + local b16, b17, b18, b19 = word2bytes(h4); + local b20, b21, b22, b23 = word2bytes(h5); + local b24, b25, b26, b27 = word2bytes(h6); + local b28, b29, b30, b31 = word2bytes(h7); + + return string.pack(string.rep('B', 32), + b0, b1, b2, b3, b4, b5, b6, b7, b8, + b9, b10, b11, b12, b13, b14, b15, + b16, b17, b18, b19, b20, b21, b22, b23, b24, + b25, b26, b27, b28, b29, b30, b31); + end + + return public; + +end + +--- @class Stream : table + +--- @param stream (Stream) - A function that returns the next byte of the stream, or nil if the stream has ended. +--- @returns table - A table containing the hash in bytes, string, and hex formats. +local sha2_256 = function(stream) + local result = SHA2_256() + .update(stream) + .finish() + return result +end + +return { + sha2_256 = sha2_256, + SHA2_256 = SHA2_256 +}; diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_512.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_512.lua new file mode 100644 index 0000000..56298c7 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_512.lua @@ -0,0 +1,107 @@ +local Hex = require(".crypto.util.hex") + +local k512 = { +0x428a2f98d728ae22,0x7137449123ef65cd,0xb5c0fbcfec4d3b2f,0xe9b5dba58189dbbc, +0x3956c25bf348b538,0x59f111f1b605d019,0x923f82a4af194f9b,0xab1c5ed5da6d8118, +0xd807aa98a3030242,0x12835b0145706fbe,0x243185be4ee4b28c,0x550c7dc3d5ffb4e2, +0x72be5d74f27b896f,0x80deb1fe3b1696b1,0x9bdc06a725c71235,0xc19bf174cf692694, +0xe49b69c19ef14ad2,0xefbe4786384f25e3,0x0fc19dc68b8cd5b5,0x240ca1cc77ac9c65, +0x2de92c6f592b0275,0x4a7484aa6ea6e483,0x5cb0a9dcbd41fbd4,0x76f988da831153b5, +0x983e5152ee66dfab,0xa831c66d2db43210,0xb00327c898fb213f,0xbf597fc7beef0ee4, +0xc6e00bf33da88fc2,0xd5a79147930aa725,0x06ca6351e003826f,0x142929670a0e6e70, +0x27b70a8546d22ffc,0x2e1b21385c26c926,0x4d2c6dfc5ac42aed,0x53380d139d95b3df, +0x650a73548baf63de,0x766a0abb3c77b2a8,0x81c2c92e47edaee6,0x92722c851482353b, +0xa2bfe8a14cf10364,0xa81a664bbc423001,0xc24b8b70d0f89791,0xc76c51a30654be30, +0xd192e819d6ef5218,0xd69906245565a910,0xf40e35855771202a,0x106aa07032bbd1b8, +0x19a4c116b8d2d0c8,0x1e376c085141ab53,0x2748774cdf8eeb99,0x34b0bcb5e19b48a8, +0x391c0cb3c5c95a63,0x4ed8aa4ae3418acb,0x5b9cca4f7763e373,0x682e6ff3d6b2b8a3, +0x748f82ee5defb2fc,0x78a5636f43172f60,0x84c87814a1f0ab72,0x8cc702081a6439ec, +0x90befffa23631e28,0xa4506cebde82bde9,0xbef9a3f7b2c67915,0xc67178f2e372532b, +0xca273eceea26619c,0xd186b8c721c0c207,0xeada7dd6cde0eb1e,0xf57d4f7fee6ed178, +0x06f067aa72176fba,0x0a637dc5a2c898a6,0x113f9804bef90dae,0x1b710b35131c471b, +0x28db77f523047d84,0x32caab7b40c72493,0x3c9ebe0a15c9bebc,0x431d67c49c100d4c, +0x4cc5d4becb3e42b6,0x597f299cfc657e2a,0x5fcb6fab3ad6faec,0x6c44198c4a475817 +} + +local function pad128(msg, len) + local extra = 128 - ((len + 1 + 8) % 128) + len = string.pack(">I8", len * 8) + msg = msg .. "\128" .. string.rep("\0", extra) .. len + assert(#msg % 128 == 0) + return msg +end + +local ww512 = {} + +--- SHA-512 hash function. +--- @param msg string - The message to hash. +--- @returns table - A table containing the hash in bytes, string, and hex formats. +local function sha512 (msg) + msg = pad128(msg, #msg) + local h1, h2, h3, h4, h5, h6, h7, h8 = + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179 + local k = k512 + local w = ww512 + local mlen = #msg + + for i = 1, mlen, 128 do + w[1], w[2], w[3], w[4], w[5], w[6], w[7], w[8], + w[9], w[10], w[11], w[12], w[13], w[14], w[15], w[16] + = string.unpack(">i8i8i8i8i8i8i8i8i8i8i8i8i8i8i8i8", msg, i) + -- mix msg block in state + + for j = 17, 80 do + local a = w[j-15] + local b = w[j-2] + w[j] = (a >> 1 ~ a >> 7 ~ a >> 8 ~ a << 56 ~ a << 63) + + (b >> 6 ~ b >> 19 ~ b >> 61 ~ b << 3 ~ b << 45) + + w[j-7] + w[j-16] + end + local a, b, c, d, e, f, g, h = h1, h2, h3, h4, h5, h6, h7, h8 + -- main state permutation + for j = 1, 80 do + local z = (e >> 14 ~ e >> 18 ~ e >> 41 ~ e << 23 + ~ e << 46 ~ e << 50) + + (g ~ e & (f ~ g)) + h + k[j] + w[j] + h = g + g = f + f = e + e = z + d + d = c + c = b + b = a + a = z + ((a ~ c) & d ~ a & c) + + (a >> 28 ~ a >> 34 ~ a >> 39 ~ a << 25 + ~ a << 30 ~ a << 36) + end + h1 = h1 + a + h2 = h2 + b + h3 = h3 + c + h4 = h4 + d + h5 = h5 + e + h6 = h6 + f + h7 = h7 + g + h8 = h8 + h + end + + local public = {} + + public.asBytes = function() + return { h1, h2, h3, h4, h5, h6, h7, h8} + end + + public.asString = function() + return string.pack(">i8i8i8i8i8i8i8i8", h1, h2, h3, h4, h5, h6, h7, h8) + end + + public.asHex = function() + return Hex.stringToHex(string.pack(">i8i8i8i8i8i8i8i8", h1, h2, h3, h4, h5, h6, h7, h8)) + end + + return public +end + +return sha512 diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha3.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha3.lua new file mode 100644 index 0000000..bef4656 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha3.lua @@ -0,0 +1,235 @@ +local Hex = require(".crypto.util.hex"); + +local ROUNDS = 24 + +local roundConstants = { +0x0000000000000001, +0x0000000000008082, +0x800000000000808A, +0x8000000080008000, +0x000000000000808B, +0x0000000080000001, +0x8000000080008081, +0x8000000000008009, +0x000000000000008A, +0x0000000000000088, +0x0000000080008009, +0x000000008000000A, +0x000000008000808B, +0x800000000000008B, +0x8000000000008089, +0x8000000000008003, +0x8000000000008002, +0x8000000000000080, +0x000000000000800A, +0x800000008000000A, +0x8000000080008081, +0x8000000000008080, +0x0000000080000001, +0x8000000080008008 +} + +local rotationOffsets = { +-- ordered for [x][y] dereferencing, so appear flipped here: +{0, 36, 3, 41, 18}, +{1, 44, 10, 45, 2}, +{62, 6, 43, 15, 61}, +{28, 55, 25, 21, 56}, +{27, 20, 39, 8, 14} +} + + + +-- the full permutation function +local function keccakF(st) + local permuted = st.permuted + local parities = st.parities + for round = 1, ROUNDS do + -- theta() + for x = 1,5 do + parities[x] = 0 + local sx = st[x] + for y = 1,5 do parities[x] = parities[x] ~ sx[y] end + end + -- + -- unroll the following loop + --for x = 1,5 do + -- local p5 = parities[(x)%5 + 1] + -- local flip = parities[(x-2)%5 + 1] ~ ( p5 << 1 | p5 >> 63) + -- for y = 1,5 do st[x][y] = st[x][y] ~ flip end + --end + local p5, flip, s + --x=1 + p5 = parities[2] + flip = parities[5] ~ (p5 << 1 | p5 >> 63) + s = st[1] + for y = 1,5 do s[y] = s[y] ~ flip end + --x=2 + p5 = parities[3] + flip = parities[1] ~ (p5 << 1 | p5 >> 63) + s = st[2] + for y = 1,5 do s[y] = s[y] ~ flip end + --x=3 + p5 = parities[4] + flip = parities[2] ~ (p5 << 1 | p5 >> 63) + s = st[3] + for y = 1,5 do s[y] = s[y] ~ flip end + --x=4 + p5 = parities[5] + flip = parities[3] ~ (p5 << 1 | p5 >> 63) + s = st[4] + for y = 1,5 do s[y] = s[y] ~ flip end + --x=5 + p5 = parities[1] + flip = parities[4] ~ (p5 << 1 | p5 >> 63) + s = st[5] + for y = 1,5 do s[y] = s[y] ~ flip end + + -- rhopi() + for y = 1,5 do + local py = permuted[y] + local r + for x = 1,5 do + s, r = st[x][y], rotationOffsets[x][y] + py[(2*x + 3*y)%5 + 1] = (s << r | s >> (64-r)) + end + end + + -- chi() - unroll the loop + --for x = 1,5 do + -- for y = 1,5 do + -- local combined = (~ permuted[(x)%5 +1][y]) & permuted[(x+1)%5 +1][y] + -- st[x][y] = permuted[x][y] ~ combined + -- end + --end + + local p, p1, p2 + --x=1 + s, p, p1, p2 = st[1], permuted[1], permuted[2], permuted[3] + for y = 1,5 do s[y] = p[y] ~ (~ p1[y]) & p2[y] end + --x=2 + s, p, p1, p2 = st[2], permuted[2], permuted[3], permuted[4] + for y = 1,5 do s[y] = p[y] ~ (~ p1[y]) & p2[y] end + --x=3 + s, p, p1, p2 = st[3], permuted[3], permuted[4], permuted[5] + for y = 1,5 do s[y] = p[y] ~ (~ p1[y]) & p2[y] end + --x=4 + s, p, p1, p2 = st[4], permuted[4], permuted[5], permuted[1] + for y = 1,5 do s[y] = p[y] ~ (~ p1[y]) & p2[y] end + --x=5 + s, p, p1, p2 = st[5], permuted[5], permuted[1], permuted[2] + for y = 1,5 do s[y] = p[y] ~ (~ p1[y]) & p2[y] end + + -- iota() + st[1][1] = st[1][1] ~ roundConstants[round] + end +end + + +local function absorb(st, buffer, algorithm) + + local blockBytes = st.rate / 8 + local blockWords = blockBytes / 8 + + -- append 0x01 byte and pad with zeros to block size (rate/8 bytes) + local totalBytes = #buffer + 1 + -- for keccak (2012 submission), the padding is byte 0x01 followed by zeros + -- for SHA3 (NIST, 2015), the padding is byte 0x06 followed by zeros + + if algorithm == "keccak" then + buffer = buffer .. ( '\x01' .. string.char(0):rep(blockBytes - (totalBytes % blockBytes))) + end + + if algorithm == "sha3" then + buffer = buffer .. ( '\x06' .. string.char(0):rep(blockBytes - (totalBytes % blockBytes))) + end + + totalBytes = #buffer + + --convert data to an array of u64 + local words = {} + for i = 1, totalBytes - (totalBytes % 8), 8 do + words[#words + 1] = string.unpack(' 1) then + out = Array.XOR(out, s); + else + out = s; + end + end + + return out; + end + + public.finish = function() + local blocks = math.ceil(dKeyLen / blockLen); + + dKey = {}; + + for b = 1, blocks do + local block = buildBlock(b); + dKey = Array.concat(dKey, block); + end + + if(Array.size(dKey) > dKeyLen) then dKey = Array.truncate(dKey, dKeyLen); end + + return public; + end + + public.asBytes = function() + return dKey; + end + + public.asHex = function() + return Array.toHex(dKey); + end + + public.asString = function() + return Array.toString(dKey); + end + + return public; +end + +--- @class Array : table + +--- PBKDF2 key derivation function +--- @param password (Array) - The password to derive the key from +--- @param salt (Array) - The salt to use +--- @param iterations number - The number of iterations to perform +--- @param keyLen number - The length of the key to derive +--- @param digest? string - The digest algorithm to use (sha1, sha256). Defaults to sha1. +--- @returns string - The derived key +local pbkdf2 = function(password, salt, iterations, keyLen, digest) + local Digest = nil + if digest == "sha1" then + Digest = SHA1.SHA1 + elseif digest == "sha256" then + Digest = SHA2_256.SHA2_256 + elseif digest == nil then + Digest = SHA1.SHA1 + else + error("Unsupported algorithm: " .. digest) + end + + local prf = HMAC.HMAC().setBlockSize(64).setDigest(Digest); + + local res = PBKDF2() + .setPRF(prf) + .setBlockLen(16) + .setDKeyLen(keyLen) + .setIterations(iterations) + .setSalt(salt) + .setPassword(password) + .finish() + + return res +end + +return { + PBKDF2 = PBKDF2, + pbkdf2 = pbkdf2 +}; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/hmac.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/hmac.lua new file mode 100644 index 0000000..470c4bf --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/hmac.lua @@ -0,0 +1,126 @@ +local Bit = require(".crypto.util.bit"); +local Stream = require(".crypto.util.stream"); +local Array = require(".crypto.util.array"); + +local SHA1 = require(".crypto.digest.sha1"); +local SHA2_256 = require(".crypto.digest.sha2_256"); + +local XOR = Bit.bxor; + +local HMAC = function() + local public = {}; + local blockSize = 64; + local Digest = nil; + local outerPadding = {}; + local innerPadding = {} + local digest; + + public.setBlockSize = function(bytes) + blockSize = bytes; + return public; + end + + public.setDigest = function(digestModule) + Digest = digestModule; + digest = Digest(); + return public; + end + + public.setKey = function(key) + local keyStream; + if Digest == nil then + error("Digest not set"); + end + if (Array.size(key) > blockSize) then + keyStream = Stream.fromArray(Digest() + .update(Stream.fromArray(key)) + .finish() + .asBytes()); + else + keyStream = Stream.fromArray(key); + end + + outerPadding = {}; + innerPadding = {}; + + for i = 1, blockSize do + local byte = keyStream(); + if byte == nil then byte = 0x00; end + outerPadding[i] = XOR(0x5C, byte); + innerPadding[i] = XOR(0x36, byte); + end + + return public; + end + + public.init = function() + digest.init() + .update(Stream.fromArray(innerPadding)); + return public; + end + + public.update = function(messageStream) + digest.update(messageStream); + return public; + end + + public.finish = function() + local inner = digest.finish().asBytes(); + digest.init() + .update(Stream.fromArray(outerPadding)) + .update(Stream.fromArray(inner)) + .finish(); + + return public; + end + + public.asBytes = function() + return digest.asBytes(); + end + + public.asHex = function() + return digest.asHex(); + end + + public.asString = function() + return digest.asString(); + end + + return public; +end + +--- @class Array : table +--- @class Stream : table + +--- HMAC function for generating a hash-based message authentication code +--- @param data (Stream) - The data to hash and authenticate +--- @param key (Array) - The key to use for the HMAC +--- @param algorithm? (string) - The algorithm to use for the HMAC (sha1, sha256). Defaults to "sha1" +--- @returns table - A table containing the HMAC in bytes, string, and hex formats. +local hmac = function(data, key, algorithm) + local digest = nil + if algorithm == "sha1" then + digest = SHA1.SHA1 + elseif algorithm == "sha256" then + digest = SHA2_256.SHA2_256 + elseif algorithm == nil then + digest = SHA1.SHA1 + else + error("Unsupported algorithm: " .. algorithm) + end + + local res = HMAC() + .setBlockSize(32) + .setDigest(digest) + .setKey(key) + .init() + .update(data) + .finish() + + return res +end + +return { + hmac = hmac, + HMAC = HMAC +}; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/init.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/init.lua new file mode 100644 index 0000000..65a507d --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/init.lua @@ -0,0 +1,8 @@ +local Hmac = require(".crypto.mac.hmac") + +local mac = { + _version = "0.0.1", + createHmac = Hmac.hmac, +}; + +return mac \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/padding/zero.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/padding/zero.lua new file mode 100644 index 0000000..0a4f614 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/padding/zero.lua @@ -0,0 +1,17 @@ +local ZeroPadding = function(blockSize, byteCount) + + local paddingCount = blockSize - ((byteCount -1) % blockSize) + 1; + local bytesLeft = paddingCount; + + local stream = function() + if bytesLeft > 0 then + bytesLeft = bytesLeft - 1; + return 0x00; + else + return nil; + end + end + return stream; +end + +return ZeroPadding; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/array.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/array.lua new file mode 100644 index 0000000..4c0be04 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/array.lua @@ -0,0 +1,222 @@ + +local Bit = require(".crypto.util.bit"); +local Queue = require(".crypto.util.queue"); + +local XOR = Bit.bxor; + +local Array = {}; + +Array.size = function(array) + return #array; +end + +Array.fromString = function(string) + local bytes = {}; + + local i = 1; + local byte = string.byte(string, i); + while byte ~= nil do + bytes[i] = byte; + i = i + 1; + byte = string.byte(string, i); + end + + return bytes; + +end + +Array.toString = function(bytes) + local chars = {}; + local i = 1; + + local byte = bytes[i]; + while byte ~= nil do + chars[i] = string.char(byte); + i = i + 1; + byte = bytes[i]; + end + + return table.concat(chars, ""); +end + +Array.fromStream = function(stream) + local array = {}; + local i = 1; + + local byte = stream(); + while byte ~= nil do + array[i] = byte; + i = i + 1; + byte = stream(); + end + + return array; +end + +Array.readFromQueue = function(queue, size) + local array = {}; + + for i = 1, size do + array[i] = queue.pop(); + end + + return array; +end + +Array.writeToQueue = function(queue, array) + local size = Array.size(array); + + for i = 1, size do + queue.push(array[i]); + end +end + +Array.toStream = function(array) + local queue = Queue(); + local i = 1; + + local byte = array[i]; + while byte ~= nil do + queue.push(byte); + i = i + 1; + byte = array[i]; + end + + return queue.pop; +end + + +local fromHexTable = {}; +for i = 0, 255 do + fromHexTable[string.format("%02X", i)] = i; + fromHexTable[string.format("%02x", i)] = i; +end + +Array.fromHex = function(hex) + local array = {}; + + for i = 1, string.len(hex) / 2 do + local h = string.sub(hex, i * 2 - 1, i * 2); + array[i] = fromHexTable[h]; + end + + return array; +end + + +local toHexTable = {}; +for i = 0, 255 do + toHexTable[i] = string.format("%02X", i); +end + +Array.toHex = function(array) + local hex = {}; + local i = 1; + + local byte = array[i]; + while byte ~= nil do + hex[i] = toHexTable[byte]; + i = i + 1; + byte = array[i]; + end + + return table.concat(hex, ""); + +end + +Array.concat = function(a, b) + local concat = {}; + local out = 1; + + local i = 1; + local byte = a[i]; + while byte ~= nil do + concat[out] = byte; + i = i + 1; + out = out + 1; + byte = a[i]; + end + + i = 1; + byte = b[i]; + while byte ~= nil do + concat[out] = byte; + i = i + 1; + out = out + 1; + byte = b[i]; + end + + return concat; +end + +Array.truncate = function(a, newSize) + local x = {}; + + for i = 1, newSize do + x[i] = a[i]; + end + + return x; +end + +Array.XOR = function(a, b) + local x = {}; + + for k, v in pairs(a) do + x[k] = XOR(v, b[k]); + end + + return x; +end + +Array.substitute = function(input, sbox) + local out = {}; + + for k, v in pairs(input) do + out[k] = sbox[v]; + end + + return out; +end + +Array.permute = function(input, pbox) + local out = {}; + + for k, v in pairs(pbox) do + out[k] = input[v]; + end + + return out; +end + +Array.copy = function(input) + local out = {}; + + for k, v in pairs(input) do + out[k] = v; + end + return out; +end + +Array.slice = function(input, start, stop) + local out = {}; + + if start == nil then + start = 1 + elseif start < 0 then + start = #input + start + 1 + end + if stop == nil then + stop = #input + elseif stop < 0 then + stop = #input + stop + 1 + end + + for i = start, stop do + table.insert(out, input[i]) + end + + return out; +end + +return Array; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/bit.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/bit.lua new file mode 100644 index 0000000..0bbe448 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/bit.lua @@ -0,0 +1,44 @@ +local ok, e +ok = nil +if not ok then + ok, e = pcall(require, "bit") -- the LuaJIT one ? +end +if not ok then + ok, e = pcall(require, "bit32") -- Lua 5.2 +end +if not ok then + ok, e = pcall(require, "bit.numberlua") -- for Lua 5.1, https://github.com/tst2005/lua-bit-numberlua/ +end +if not ok then + error("no bitwise support found", 2) +end +assert(type(e) == "table", "invalid bit module") + +-- Workaround to support Lua 5.2 bit32 API with the LuaJIT bit one +if e.rol and not e.lrotate then + e.lrotate = e.rol +end +if e.ror and not e.rrotate then + e.rrotate = e.ror +end + +-- Workaround to support incomplete bit operations set +if not e.ror and not e.rrotate then + local ror = function(b, n) + return e.bor(e.rshift(b, n), e.lshift(b, 32 - n)) + end + + e.ror = ror + e.rrotate = ror +end + +if not e.rol and not e.lrotate then + local rol = function(b, n) + return e.bor(e.lshift(b, n), e.rshift(b, 32 - n)) + end + + e.rol = rol + e.lrotate = rol +end + +return e \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/hex.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/hex.lua new file mode 100644 index 0000000..bcc7f2a --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/hex.lua @@ -0,0 +1,46 @@ + +--- Converts a string to its hexadecimal representation. +--- @param s string The input string. +--- @param ln? number - The number of characters per line. If not provided, the output will be a single line. +--- @param sep? string - The separator between each pair of hexadecimal characters. Defaults to an empty string. +--- @return string The - hexadecimal representation of the input string. +local function stringToHex(s, ln, sep) + if #s == 0 then return "" end + if not ln then + return (s:gsub('.', + function(c) return string.format('%02x', string.byte(c)) end + )) + end + sep = sep or "" + local t = {} + for i = 1, #s - 1 do + t[#t + 1] = string.format("%02x%s", s:byte(i), + (i % ln == 0) and '\n' or sep) + end + t[#t + 1] = string.format("%02x", s:byte(#s)) + return table.concat(t) +end + +--- Converts a hex encoded string to its corresponding decoded string. +--- If the optional parameter `unsafe` is defined, it assumes that the hex string is well-formed +--- (no checks, no whitespace removal). By default, it removes whitespace (including newlines) +--- and checks that the hex string is well-formed. +--- @param hs (string) The hex encoded string to be decoded. +--- @param unsafe (boolean) [optional] If true, assumes the hex string is well-formed. +--- @return (string) The decoded string. +local function hexToString(hs, unsafe) + local tonumber = tonumber + if not unsafe then + hs = string.gsub(hs, "%s+", "") -- remove whitespaces + if string.find(hs, '[^0-9A-Za-z]') or #hs % 2 ~= 0 then + error("invalid hex string") + end + end + local count = string.gsub(hs, '(%x%x)',function(c) return string.char(tonumber(c, 16)) end) + return count +end + +return { + stringToHex = stringToHex, + hexToString = hexToString, +} \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/init.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/init.lua new file mode 100644 index 0000000..80afdda --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/init.lua @@ -0,0 +1,16 @@ +local Bit = require(".crypto.util.bit") +local Queue = require(".crypto.util.queue") +local Stream = require(".crypto.util.stream") +local Hex = require(".crypto.util.hex") +local Array = require(".crypto.util.array") + +local util = { + _version = "0.0.1", + bit = Bit, + queue = Queue, + stream = Stream, + hex = Hex, + array = Array, +} + +return util diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/queue.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/queue.lua new file mode 100644 index 0000000..4818192 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/queue.lua @@ -0,0 +1,47 @@ +local Queue = function() + local queue = {}; + local tail = 0; + local head = 0; + + local public = {}; + + public.push = function(obj) + queue[head] = obj; + head = head + 1; + return; + end + + public.pop = function() + if tail < head + then + local obj = queue[tail]; + queue[tail] = nil; + tail = tail + 1; + return obj; + else + return nil; + end + end + + public.size = function() + return head - tail; + end + + public.getHead = function() + return head; + end + + public.getTail = function() + return tail; + end + + public.reset = function() + queue = {}; + head = 0; + tail = 0; + end + + return public; +end + +return Queue; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/stream.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/stream.lua new file mode 100644 index 0000000..49d52f5 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/stream.lua @@ -0,0 +1,98 @@ +local Queue = require(".crypto.util.queue"); + +local Stream = {}; + + +Stream.fromString = function(string) + local i = 0; + return function() + i = i + 1; + return string.byte(string, i); + end +end + + +Stream.toString = function(stream) + local array = {}; + local i = 1; + + local byte = stream(); + while byte ~= nil do + array[i] = string.char(byte); + i = i + 1; + byte = stream(); + end + + return table.concat(array); +end + + +Stream.fromArray = function(array) + local queue = Queue(); + local i = 1; + + local byte = array[i]; + while byte ~= nil do + queue.push(byte); + i = i + 1; + byte = array[i]; + end + + return queue.pop; +end + + +Stream.toArray = function(stream) + local array = {}; + local i = 1; + + local byte = stream(); + while byte ~= nil do + array[i] = byte; + i = i + 1; + byte = stream(); + end + + return array; +end + + +local fromHexTable = {}; +for i = 0, 255 do + fromHexTable[string.format("%02X", i)] = i; + fromHexTable[string.format("%02x", i)] = i; +end + +Stream.fromHex = function(hex) + local queue = Queue(); + + for i = 1, string.len(hex) / 2 do + local h = string.sub(hex, i * 2 - 1, i * 2); + queue.push(fromHexTable[h]); + end + + return queue.pop; +end + + + +local toHexTable = {}; +for i = 0, 255 do + toHexTable[i] = string.format("%02X", i); +end + +Stream.toHex = function(stream) + local hex = {}; + local i = 1; + + local byte = stream(); + while byte ~= nil do + hex[i] = toHexTable[byte]; + i = i + 1; + byte = stream(); + end + + return table.concat(hex); +end + +return Stream; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/default.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/default.lua new file mode 100644 index 0000000..54b935a --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/default.lua @@ -0,0 +1,22 @@ +-- default handler for aos +return function (insertInbox) + return function (msg) + -- Add Message to Inbox + insertInbox(msg) + + local txt = Colors.gray .. "New Message From " .. Colors.green .. + (msg.From and (msg.From:sub(1,3) .. "..." .. msg.From:sub(-3)) or "unknown") .. Colors.gray .. ": " + if msg.Action then + txt = txt .. Colors.gray .. (msg.Action and ("Action = " .. Colors.blue .. msg.Action:sub(1,20)) or "") .. Colors.reset + else + local data = msg.Data + if type(data) == 'table' then + data = require('json').encode(data) + end + txt = txt .. Colors.gray .. "Data = " .. Colors.blue .. (data and data:sub(1,20) or "") .. Colors.reset + end + -- Print to Output + print(txt) + end + +end \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/dump.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/dump.lua new file mode 100644 index 0000000..d59e22f --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/dump.lua @@ -0,0 +1,297 @@ +-- +-- Copyright (C) 2018 Masatoshi Teruya +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +-- THE SOFTWARE. +-- +-- dump.lua +-- lua-dump +-- Created by Masatoshi Teruya on 18/04/22. +-- +--- file-scope variables +local type = type +local floor = math.floor +local tostring = tostring +local tblsort = table.sort +local tblconcat = table.concat +local strmatch = string.match +local strformat = string.format +--- constants +local INFINITE_POS = math.huge +local LUA_FIELDNAME_PAT = '^[a-zA-Z_][a-zA-Z0-9_]*$' +local FOR_KEY = 'key' +local FOR_VAL = 'val' +local FOR_CIRCULAR = 'circular' +local RESERVED_WORD = { + -- primitive data + ['nil'] = true, + ['true'] = true, + ['false'] = true, + -- declaraton + ['local'] = true, + ['function'] = true, + -- boolean logic + ['and'] = true, + ['or'] = true, + ['not'] = true, + -- conditional statement + ['if'] = true, + ['elseif'] = true, + ['else'] = true, + -- iteration statement + ['for'] = true, + ['in'] = true, + ['while'] = true, + ['until'] = true, + ['repeat'] = true, + -- jump statement + ['break'] = true, + ['goto'] = true, + ['return'] = true, + -- block scope statement + ['then'] = true, + ['do'] = true, + ['end'] = true, +} +local DEFAULT_INDENT = 4 + +--- filter function for dump +--- @param val any +--- @param depth integer +--- @param vtype string +--- @param use string +--- @param key any +--- @param udata any +--- @return any val +--- @return boolean nodump +local function DEFAULT_FILTER(val) + return val +end + +--- sort_index +--- @param a table +--- @param b table +local function sort_index(a, b) + if a.typ == b.typ then + if a.typ == 'boolean' then + return b.key + end + + return a.key < b.key + end + + return a.typ == 'number' +end + +--- dumptbl +--- @param tbl table +--- @param depth integer +--- @param indent string +--- @param nestIndent string +--- @param ctx table +--- @return string +local function dumptbl(tbl, depth, indent, nestIndent, ctx) + local ref = tostring(tbl) + + -- circular reference + if ctx.circular[ref] then + local val, nodump = ctx.filter(tbl, depth, type(tbl), FOR_CIRCULAR, tbl, + ctx.udata) + + if val ~= nil and val ~= tbl then + local t = type(val) + + if t == 'table' then + -- dump table value + if not nodump then + return dumptbl(val, depth + 1, indent, nestIndent, ctx) + end + return tostring(val) + elseif t == 'string' then + return strformat('%q', val) + elseif t == 'number' or t == 'boolean' then + return tostring(val) + end + + return strformat('%q', tostring(val)) + end + + return '""' + end + + local res = {} + local arr = {} + local narr = 0 + local fieldIndent = indent .. nestIndent + + -- save reference + ctx.circular[ref] = true + + for k, v in pairs(tbl) do + -- check key + local key, nokdump = ctx.filter(k, depth, type(k), FOR_KEY, nil, + ctx.udata) + + if key ~= nil then + -- check val + local val, novdump = ctx.filter(v, depth, type(v), FOR_VAL, key, + ctx.udata) + local kv + + if val ~= nil then + local kt = type(key) + local vt = type(val) + + -- convert key to suitable to be safely read back + -- by the Lua interpreter + if kt == 'number' or kt == 'boolean' then + k = key + key = '[' .. tostring(key) .. ']' + -- dump table value + elseif kt == 'table' and not nokdump then + key = '[' .. + dumptbl(key, depth + 1, fieldIndent, nestIndent, + ctx) .. ']' + k = key + kt = 'string' + elseif kt ~= 'string' or RESERVED_WORD[key] or + not strmatch(key, LUA_FIELDNAME_PAT) then + key = strformat("[%q]", tostring(key), v) + k = key + kt = 'string' + end + + -- convert key-val pair to suitable to be safely read back + -- by the Lua interpreter + if vt == 'number' or vt == 'boolean' then + kv = strformat('%s%s = %s', fieldIndent, key, tostring(val)) + elseif vt == 'string' then + -- dump a string-value + if not novdump then + kv = strformat('%s%s = %q', fieldIndent, key, val) + else + kv = strformat('%s%s = %s', fieldIndent, key, val) + end + elseif vt == 'table' and not novdump then + kv = strformat('%s%s = %s', fieldIndent, key, dumptbl(val, + depth + + 1, + fieldIndent, + nestIndent, + ctx)) + else + kv = strformat('%s%s = %q', fieldIndent, key, tostring(val)) + end + + -- add to array + narr = narr + 1 + arr[narr] = { + typ = kt, + key = k, + val = kv, + } + end + end + end + + -- remove reference + ctx.circular[ref] = nil + -- concat result + if narr > 0 then + tblsort(arr, sort_index) + + for i = 1, narr do + res[i] = arr[i].val + end + res[1] = '{' .. ctx.LF .. res[1] + res = tblconcat(res, ',' .. ctx.LF) .. ctx.LF .. indent .. '}' + else + res = '{}' + end + + return res +end + +--- is_uint +--- @param v any +--- @return boolean ok +local function is_uint(v) + return type(v) == 'number' and v < INFINITE_POS and v >= 0 and floor(v) == v +end + +--- dump +--- @param val any +--- @param indent integer +--- @param padding integer +--- @param filter function +--- @param udata +--- @return string +local function dump(val, indent, padding, filter, udata) + local t = type(val) + + -- check indent + if indent == nil then + indent = DEFAULT_INDENT + elseif not is_uint(indent) then + error('indent must be unsigned integer', 2) + end + + -- check padding + if padding == nil then + padding = 0 + elseif not is_uint(padding) then + error('padding must be unsigned integer', 2) + end + + -- check filter + if filter == nil then + filter = DEFAULT_FILTER + elseif type(filter) ~= 'function' then + error('filter must be function', 2) + end + + -- dump table + if t == 'table' then + local ispace = '' + local pspace = '' + + if indent > 0 then + ispace = strformat('%' .. tostring(indent) .. 's', '') + end + + if padding > 0 then + pspace = strformat('%' .. tostring(padding) .. 's', '') + end + + return dumptbl(val, 1, pspace, ispace, { + LF = ispace == '' and ' ' or '\n', + circular = {}, + filter = filter, + udata = udata, + }) + end + + -- dump value + local v, nodump = filter(val, 0, t, FOR_VAL, nil, udata) + if nodump == true then + return tostring(v) + end + return strformat('%q', tostring(v)) +end + +return dump \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/eval.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/eval.lua new file mode 100644 index 0000000..da3c629 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/eval.lua @@ -0,0 +1,38 @@ +local stringify = require(".stringify") +-- handler for eval +return function (ao) + return function (msg) + -- exec expression + local expr = msg.Data + local func, err = load("return " .. expr, 'aos', 't', _G) + local output = "" + local e = nil + if err then + func, err = load(expr, 'aos', 't', _G) + end + if func then + output, e = func() + else + ao.outbox.Error = err + return + end + if e then + ao.outbox.Error = e + return + end + if HANDLER_PRINT_LOGS and output then + table.insert(HANDLER_PRINT_LOGS, type(output) == "table" and stringify.format(output) or tostring(output)) + else + -- set result in outbox.Output (Left for backwards compatibility) + ao.outbox.Output = { + json = type(output) == "table" and pcall(function () return json.encode(output) end) and output or "undefined", + data = { + output = type(output) == "table" and stringify.format(output) or output, + prompt = Prompt() + }, + prompt = Prompt() + } + + end + end +end diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.lua new file mode 100644 index 0000000..ab6d53f --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.lua @@ -0,0 +1,59 @@ +local _utils = { _version = "0.0.2" } + +local _ = require('.utils') +local ao = require(".ao") + +function _utils.hasMatchingTag(name, value) + assert(type(name) == 'string' and type(value) == 'string', 'invalid arguments: (name : string, value : string)') + + return function (msg) + return msg.Tags[name] == value + end +end + +function _utils.hasMatchingTagOf(name, values) + assert(type(name) == 'string' and type(values) == 'table', 'invalid arguments: (name : string, values : string[])') + return function (msg) + for _, value in ipairs(values) do + local patternResult = Handlers.utils.hasMatchingTag(name, value)(msg) + + if patternResult ~= 0 and patternResult ~= false and patternResult ~= "skip" then + return patternResult + end + end + + return 0 + end +end + +function _utils.hasMatchingData(value) + assert(type(value) == 'string', 'invalid arguments: (value : string)') + return function (msg) + return msg.Data == value + end +end + +function _utils.reply(input) + assert(type(input) == 'table' or type(input) == 'string', 'invalid arguments: (input : table or string)') + return function (msg) + if type(input) == 'string' then + msg.reply({ Data = input}) + return + end + msg.reply(input) + end +end + +function _utils.continue(fn) + assert(type(fn) == 'function', 'invalid arguments: (fn : function)') + return function (msg) + local patternResult = fn(msg) + + if not patternResult or patternResult == 0 or patternResult == "skip" then + return patternResult + end + return 1 + end +end + +return _utils \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.md b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.md new file mode 100644 index 0000000..a09793b --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.md @@ -0,0 +1,138 @@ +# README for Lua Module: \_utils (v0.0.1) + +## Overview + +The `_utils` module is a lightweight Lua utility library designed to provide common functionalities for handling and processing messages within the AOS computer system. It offers a set of functions to check message attributes and send replies, simplifying the development of more complex scripts and modules. This document will guide you through the module's functionalities, installation, and usage. + +### Version + +0.0.1 + +## Installation + +1. Ensure you have Lua installed on your AOS computer system. +2. Copy the `_utils.lua` file to your project directory or a designated Lua libraries directory. +3. Include the module in your Lua scripts using `local _utils = require('_utils')`. + +## Features + +### hasMatchingTag(name, value) + +Checks if a given message has a tag that matches the specified name and value. + +- **Parameters:** + + - `name` (string): The name of the tag to check. + - `value` (string): The value of the tag to match. + +- **Returns:** Function that takes a message object and returns `-1` if the tag matches, `0` otherwise. + +### hasMatchingTagOf(name, values) + +Checks if a given message has a tag that matches the specified name and one of the specified values. + +- **Parameters:** + + - `name` (string): The name of the tag to check. + - `values` (string): The values of which one should match. + +- **Returns:** Function that takes a message object and returns `-1` if the tag matches, `0` otherwise. + +### hasMatchingData(value) + +Checks if the message data matches the specified value. + +- **Parameters:** + + - `value` (string): The value to match against the message data. + +- **Returns:** Function that takes a message object and returns `-1` if the data matches, `0` otherwise. + +### reply(input) + +Sends a reply to the sender of a message. The reply can be a simple string or a table with more complex data and tags. + +- **Parameters:** + + - `input` (table or string): The content to send back. If a string, it sends it as data. If a table, it assumes a structure with `Tags`. + +- **Returns:** Function that takes a message object and sends the specified reply. + +### continue(fn) + +Inverts the provided pattern matching function's result if it matches, so that it continues execution with the next matching handler. + +- **Parameters:** + + - `fn` (function): Pattern matching function that returns `"skip"`, `false` or `0` if it does not match. + +- **Returns:** Function that executes the pattern matching function and returns `1` (continue), so that the execution of handlers continues. + +## Usage + +1. **Import the module:** + + ```lua + local _utils = require('_utils') + ``` + +2. **Check for a specific tag in a message:** + + ```lua + local isUrgent = _utils.hasMatchingTag('priority', 'urgent') + if isUrgent(message) == -1 then + print('This is an urgent message!') + end + ``` + +3. **Check for a specific tag with multiple possible values allowed:** + + ```lua + local isNotUrgent = _utils.hasMatchingTagOf('priority', { 'trivial', 'unimportant' }) + if isNotUrgent(message) == -1 then + print('This is not an urgent message!') + end + ``` + +4. **Check if the message data matches a value:** + + ```lua + local isHello = _utils.hasMatchingData('Hello') + if isHello(message) == -1 then + print('Someone says Hello!') + end + ``` + +5. **Reply to a message:** + + ```lua + local replyWithText = _utils.reply('Thank you for your message!') + replyWithText(message) + ``` + + Or with complex data and tags: + + ```lua + local replyWithTable = _utils.reply({Tags = {status = 'received'}}) + replyWithTable(message) + ``` + +6. **Continue execution shortcut:** + + ```lua + local isUrgent = _utils.continue(_utils.hasMatchingTag('priority', 'urgent')) + if isUrgent(message) ~= 0 then + print('This is an urgent message!') + end + if isUrgent(message) == -1 then return end + print('This message will continue') + ``` + +## Conventions and Requirements + +- This module assumes that the message objects provided to functions follow a specific structure with `Tags` and `Data` attributes. +- Error handling is implemented using assertions. Ensure that your AOS environment appropriately handles or logs assertion failures. + +## License + +MIT diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.lua new file mode 100644 index 0000000..6507a91 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.lua @@ -0,0 +1,302 @@ +local handlers = { _version = "0.0.5" } +local coroutine = require('coroutine') +local utils = require('.utils') + +handlers.utils = require('.handlers-utils') +-- if update we need to keep defined handlers +if Handlers then + handlers.list = Handlers.list or {} + handlers.coroutines = Handlers.coroutines or {} +else + handlers.list = {} + handlers.coroutines = {} + +end +handlers.onceNonce = 0 + + +local function findIndexByProp(array, prop, value) + for index, object in ipairs(array) do + if object[prop] == value then + return index + end + end + return nil +end + +local function assertAddArgs(name, pattern, handle, maxRuns) + assert( + type(name) == 'string' and + (type(pattern) == 'function' or type(pattern) == 'table' or type(pattern) == 'string'), + 'Invalid arguments given. Expected: \n' .. + '\tname : string, ' .. + '\tpattern : Action : string | MsgMatch : table,\n' .. + '\t\tfunction(msg: Message) : {-1 = break, 0 = skip, 1 = continue},\n' .. + '\thandle(msg : Message) : void) | Resolver,\n' .. + '\tMaxRuns? : number | "inf" | nil') +end + +function handlers.generateResolver(resolveSpec) + return function(msg) + -- If the resolver is a single function, call it. + -- Else, find the first matching pattern (by its matchSpec), and exec. + if type(resolveSpec) == "function" then + return resolveSpec(msg) + else + for matchSpec, func in pairs(resolveSpec) do + if utils.matchesSpec(msg, matchSpec) then + return func(msg) + end + end + end + end +end + +-- Returns the next message that matches the pattern +-- This function uses Lua's coroutines under-the-hood to add a handler, pause, +-- and then resume the current coroutine. This allows us to effectively block +-- processing of one message until another is received that matches the pattern. +function handlers.receive(pattern) + local self = coroutine.running() + handlers.once(pattern, function (msg) + coroutine.resume(self, msg) + end) + return coroutine.yield(pattern) +end + +function handlers.once(...) + local name, pattern, handle + if select("#", ...) == 3 then + name = select(1, ...) + pattern = select(2, ...) + handle = select(3, ...) + else + name = "_once_" .. tostring(handlers.onceNonce) + handlers.onceNonce = handlers.onceNonce + 1 + pattern = select(1, ...) + handle = select(2, ...) + end + handlers.add(name, pattern, handle, 1) +end + +function handlers.add(...) + local name, pattern, handle, maxRuns + local args = select("#", ...) + if args == 2 then + name = select(1, ...) + pattern = select(1, ...) + handle = select(2, ...) + maxRuns = nil + elseif args == 3 then + name = select(1, ...) + pattern = select(2, ...) + handle = select(3, ...) + maxRuns = nil + else + name = select(1, ...) + pattern = select(2, ...) + handle = select(3, ...) + maxRuns = select(4, ...) + end + assertAddArgs(name, pattern, handle, maxRuns) + + handle = handlers.generateResolver(handle) + + -- update existing handler by name + local idx = findIndexByProp(handlers.list, "name", name) + if idx ~= nil and idx > 0 then + -- found update + handlers.list[idx].pattern = pattern + handlers.list[idx].handle = handle + handlers.list[idx].maxRuns = maxRuns + else + -- not found then add + table.insert(handlers.list, { pattern = pattern, handle = handle, name = name, maxRuns = maxRuns }) + + end + return #handlers.list +end + +function handlers.append(...) + local name, pattern, handle, maxRuns + local args = select("#", ...) + if args == 2 then + name = select(1, ...) + pattern = select(1, ...) + handle = select(2, ...) + maxRuns = nil + elseif args == 3 then + name = select(1, ...) + pattern = select(2, ...) + handle = select(3, ...) + maxRuns = nil + else + name = select(1, ...) + pattern = select(2, ...) + handle = select(3, ...) + maxRuns = select(4, ...) + end + assertAddArgs(name, pattern, handle, maxRuns) + + handle = handlers.generateResolver(handle) + -- update existing handler by name + local idx = findIndexByProp(handlers.list, "name", name) + if idx ~= nil and idx > 0 then + -- found update + handlers.list[idx].pattern = pattern + handlers.list[idx].handle = handle + handlers.list[idx].maxRuns = maxRuns + else + + table.insert(handlers.list, { pattern = pattern, handle = handle, name = name, maxRuns = maxRuns }) + end + + +end + +function handlers.prepend(...) + local name, pattern, handle, maxRuns + local args = select("#", ...) + if args == 2 then + name = select(1, ...) + pattern = select(1, ...) + handle = select(2, ...) + maxRuns = nil + elseif args == 3 then + name = select(1, ...) + pattern = select(2, ...) + handle = select(3, ...) + maxRuns = nil + else + name = select(1, ...) + pattern = select(2, ...) + handle = select(3, ...) + maxRuns = select(4, ...) + end + assertAddArgs(name, pattern, handle, maxRuns) + + handle = handlers.generateResolver(handle) + + -- update existing handler by name + local idx = findIndexByProp(handlers.list, "name", name) + if idx ~= nil and idx > 0 then + -- found update + handlers.list[idx].pattern = pattern + handlers.list[idx].handle = handle + handlers.list[idx].maxRuns = maxRuns + else + table.insert(handlers.list, 1, { pattern = pattern, handle = handle, name = name, maxRuns = maxRuns }) + end + + +end + +function handlers.before(handleName) + assert(type(handleName) == 'string', 'Handler name MUST be a string') + + local idx = findIndexByProp(handlers.list, "name", handleName) + return { + add = function (name, pattern, handle, maxRuns) + assertAddArgs(name, pattern, handle, maxRuns) + + handle = handlers.generateResolver(handle) + + if idx then + table.insert(handlers.list, idx, { pattern = pattern, handle = handle, name = name, maxRuns = maxRuns }) + end + + end + } +end + +function handlers.after(handleName) + assert(type(handleName) == 'string', 'Handler name MUST be a string') + local idx = findIndexByProp(handlers.list, "name", handleName) + return { + add = function (name, pattern, handle, maxRuns) + assertAddArgs(name, pattern, handle, maxRuns) + + handle = handlers.generateResolver(handle) + + if idx then + table.insert(handlers.list, idx + 1, { pattern = pattern, handle = handle, name = name, maxRuns = maxRuns }) + end + + end + } + +end + +function handlers.remove(name) + assert(type(name) == 'string', 'name MUST be string') + if #handlers.list == 1 and handlers.list[1].name == name then + handlers.list = {} + + end + + local idx = findIndexByProp(handlers.list, "name", name) + table.remove(handlers.list, idx) + +end + +--- return 0 to not call handler, -1 to break after handler is called, 1 to continue +function handlers.evaluate(msg, env) + local handled = false + assert(type(msg) == 'table', 'msg is not valid') + assert(type(env) == 'table', 'env is not valid') + + for _, o in ipairs(handlers.list) do + if o.name ~= "_default" then + local match = utils.matchesSpec(msg, o.pattern) + if not (type(match) == 'number' or type(match) == 'string' or type(match) == 'boolean') then + error("Pattern result is not valid, it MUST be string, number, or boolean") + end + + -- handle boolean returns + if type(match) == "boolean" and match == true then + match = -1 + elseif type(match) == "boolean" and match == false then + match = 0 + end + + -- handle string returns + if type(match) == "string" then + if match == "continue" then + match = 1 + elseif match == "break" then + match = -1 + else + match = 0 + end + end + + if match ~= 0 then + if match < 0 then + handled = true + end + -- each handle function can accept, the msg, env + local status, err = pcall(o.handle, msg, env) + if not status then + error(err) + end + -- remove handler if maxRuns is reached. maxRuns can be either a number or "inf" + if o.maxRuns ~= nil and o.maxRuns ~= "inf" then + o.maxRuns = o.maxRuns - 1 + if o.maxRuns == 0 then + handlers.remove(o.name) + end + end + end + if match < 0 then + return handled + end + end + end + -- do default + if not handled then + local idx = findIndexByProp(handlers.list, "name", "_default") + handlers.list[idx].handle(msg,env) + end +end + +return handlers \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.md b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.md new file mode 100644 index 0000000..0c0bdd7 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.md @@ -0,0 +1,102 @@ +# Lua Library: Handlers (Version 0.0.3) + +## Overview + +The Handlers library provides a flexible way to manage and execute a series of handlers based on patterns. Each handler consists of a pattern function, a handle function, and a name. This library is suitable for scenarios where different actions need to be taken based on varying input criteria. + +## Module Structure + +- `handlers._version`: String representing the version of the Handlers library. +- `handlers.list`: Table storing the list of registered handlers. + +## Functions + +### `handlers.append(name, pattern, handle)` + +Appends a new handler to the end of the handlers list. + +#### Parameters + +- `pattern` (function): Function that determines if the handler should be executed. +- `handle` (function): The handler function to execute. +- `name` (string): A unique name for the handler. + +### `handlers.prepend(name, pattern, handle)` + +Prepends a new handler to the beginning of the handlers list. + +#### Parameters + +- Same as `handlers.append`. + +### `handlers.before(handleName)` + +Returns an object that allows adding a new handler before a specified handler. + +#### Parameters + +- `handleName` (string): The name of the handler before which the new handler will be added. + +#### Returns + +- An object with an `add` method to insert the new handler. + +### `handlers.after(handleName)` + +Returns an object that allows adding a new handler after a specified handler. + +#### Parameters + +- `handleName` (string): The name of the handler after which the new handler will be added. + +#### Returns + +- An object with an `add` method to insert the new handler. + +### `handlers.remove(name)` + +Removes a handler from the handlers list by name. + +#### Parameters + +- `name` (string): The name of the handler to be removed. + +### `handlers.evaluate(msg, env)` + +Evaluates each handler against a given message and environment. Handlers are called in the order they appear in the handlers list. + +#### Parameters + +- `msg` (table): The message to be processed by the handlers. +- `env` (table): The environment in which the handlers are executed. + +#### Returns + +- `response` (varies): The response from the handler(s). Returns a default message if no handler matches. + +## Usage Example + +```lua +local handlers = require "handlers_module_path" + +-- Define pattern and handle functions +local function myPattern(msg) + -- Determine if the handler should be executed +end + +local function myHandle(msg, env, response) + -- Handler logic +end + +-- Append a new handler +handlers.append("myHandler", myPattern, myHandle) + +-- Evaluate a message +local response = handlers.evaluate({ key = "value" }, { envKey = "envValue" }) +``` + +## Notes + +- Handlers are executed in the order they appear in `handlers.list`. +- The pattern function should return `0` to skip the handler, `-1` to break after the handler is executed, or `1` to continue with the next handler. +- The `evaluate` function can concatenate responses from multiple handlers. diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/package.json b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/package.json new file mode 100644 index 0000000..08ca6f1 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/package.json @@ -0,0 +1,16 @@ +{ + "type": "module", + "name": "process", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "dependencies": { + "@permaweb/ao-loader": "^0.0.35" + }, + "scripts": { + "build": "ao build", + "test": "node --test --experimental-wasm-memory64", + "deploy": "ao publish -w ~/.wallet.json process.wasm -t Memory-Limit -v 1-gb -t Compute-Limit -v 9000000000000 -t Module-Format -v wasm64-unknown-emscripten-draft_2024_02_15 -t AOS-Version -v 2.0.0 -t Name -v aos-xl", + "deploy-sqlite": "ao publish -w ~/.wallet.json process.wasm -t Memory-Limit -v 1-gb -t Compute-Limit -v 9000000000000 -t Module-Format -v wasm64-unknown-emscripten-draft_2024_02_15 -t AOS-Version -v 2.0.0 -t Name -v sqlite-xl" + } +} diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/pretty.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/pretty.lua new file mode 100644 index 0000000..89ea8dc --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/pretty.lua @@ -0,0 +1,20 @@ +local pretty = { _version = "0.0.1"} + +function pretty.tprint (tbl, indent) + if not indent then indent = 0 end + local output = "" + for k, v in pairs(tbl) do + local formatting = string.rep(" ", indent) .. k .. ": " + if type(v) == "table" then + output = output .. formatting .. "\n" + output = output .. pretty.tprint(v, indent+1) + elseif type(v) == 'boolean' then + output = output .. formatting .. tostring(v) .. "\n" + else + output = output .. formatting .. v .. "\n" + end + end + return output +end + +return pretty \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.lua new file mode 100644 index 0000000..30f4a33 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.lua @@ -0,0 +1,371 @@ +local pretty = require('.pretty') +local base64 = require('.base64') +local json = require('json') +local chance = require('.chance') +local crypto = require('.crypto.init') +local coroutine = require('coroutine') + +Colors = { + red = "\27[31m", + green = "\27[32m", + blue = "\27[34m", + reset = "\27[0m", + gray = "\27[90m" +} + +Bell = "\x07" + +Dump = require('.dump') +Utils = require('.utils') +Handlers = require('.handlers') +local stringify = require(".stringify") +local assignment = require('.assignment') +ao = nil +if _G.package.loaded['.ao'] then + ao = require('.ao') +elseif _G.package.loaded['ao'] then + ao = require('ao') +end +-- Implement assignable polyfills on _ao +assignment.init(ao) + +local process = { _version = "2.0.0" } +local maxInboxCount = 10000 + +-- wrap ao.send and ao.spawn for magic table +local aosend = ao.send +local aospawn = ao.spawn +ao.send = function (msg) + if msg.Data and type(msg.Data) == 'table' then + msg['Content-Type'] = 'application/json' + msg.Data = require('json').encode(msg.Data) + end + return aosend(msg) +end +ao.spawn = function (module, msg) + if msg.Data and type(msg.Data) == 'table' then + msg['Content-Type'] = 'application/json' + msg.Data = require('json').encode(msg.Data) + end + return aospawn(module, msg) +end + +local function removeLastThreeLines(input) + local lines = {} + for line in input:gmatch("([^\n]*)\n?") do + table.insert(lines, line) + end + + -- Remove the last three lines + for i = 1, 3 do + table.remove(lines) + end + + -- Concatenate the remaining lines + return table.concat(lines, "\n") +end + + +local function insertInbox(msg) + table.insert(Inbox, msg) + if #Inbox > maxInboxCount then + local overflow = #Inbox - maxInboxCount + for i = 1,overflow do + table.remove(Inbox, 1) + end + end +end + +local function findObject(array, key, value) + for i, object in ipairs(array) do + if object[key] == value then + return object + end + end + return nil +end + +function Tab(msg) + local inputs = {} + for _, o in ipairs(msg.Tags) do + if not inputs[o.name] then + inputs[o.name] = o.value + end + end + return inputs +end + +function Prompt() + return Colors.green .. Name .. Colors.gray + .. "@" .. Colors.blue .. "aos-" .. process._version .. Colors.gray + .. "[Inbox:" .. Colors.red .. tostring(#Inbox) .. Colors.gray + .. "]" .. Colors.reset .. "> " +end + +function print(a) + if type(a) == "table" then + a = stringify.format(a) + end + + local data = a + if ao.outbox.Output.data then + data = ao.outbox.Output.data .. "\n" .. a + end + ao.outbox.Output = { data = data, prompt = Prompt(), print = true } + + -- Only supported for newer version of AOS + if HANDLER_PRINT_LOGS then + table.insert(HANDLER_PRINT_LOGS, a) + return nil + end + + return tostring(a) +end + +function Send(msg) + if not msg.Target then + print("WARN: No target specified for message. Data will be stored, but no process will receive it.") + end + local result = ao.send(msg) + return { + output = "Message added to outbox", + receive = result.receive, + onReply = result.onReply + } +end + +function Spawn(...) + local module, spawnMsg + + if select("#", ...) == 1 then + spawnMsg = select(1, ...) + module = ao._module + else + module = select(1, ...) + spawnMsg = select(2, ...) + end + + if not spawnMsg then + spawnMsg = {} + end + local result = ao.spawn(module, spawnMsg) + return { + output = "Spawn process request added to outbox", + after = result.after, + receive = result.receive + } +end + +function Receive(match) + return Handlers.receive(match) +end + +function Assign(assignment) + if not ao.assign then + print("Assign is not implemented.") + return "Assign is not implemented." + end + ao.assign(assignment) + print("Assignment added to outbox.") + return 'Assignment added to outbox.' +end + +Seeded = Seeded or false + +-- this is a temporary approach... +local function stringToSeed(s) + local seed = 0 + for i = 1, #s do + local char = string.byte(s, i) + seed = seed + char + end + return seed +end + +local function initializeState(msg, env) + if not Seeded then + --math.randomseed(1234) + chance.seed(tonumber(msg['Block-Height'] .. stringToSeed(msg.Owner .. msg.Module .. msg.Id))) + math.random = function (...) + local args = {...} + local n = #args + if n == 0 then + return chance.random() + end + if n == 1 then + return chance.integer(1, args[1]) + end + if n == 2 then + return chance.integer(args[1], args[2]) + end + return chance.random() + end + Seeded = true + end + Errors = Errors or {} + Inbox = Inbox or {} + + -- temporary fix for Spawn + if not Owner then + local _from = findObject(env.Process.Tags, "name", "From-Process") + if _from then + Owner = _from.value + else + Owner = msg.From + end + end + + if not Name then + local aosName = findObject(env.Process.Tags, "name", "Name") + if aosName then + Name = aosName.value + else + Name = 'aos' + end + end + +end + +function Version() + print("version: " .. process._version) +end + +function process.handle(msg, _) + env = nil + if _.Process then + env = _ + else + env = _.env + end + + ao.init(env) + -- relocate custom tags to root message + msg = ao.normalize(msg) + -- set process id + ao.id = ao.env.Process.Id + initializeState(msg, ao.env) + HANDLER_PRINT_LOGS = {} + + -- set os.time to return msg.Timestamp + os.time = function () return msg.Timestamp end + + -- tagify msg + msg.TagArray = msg.Tags + msg.Tags = Tab(msg) + -- tagify Process + ao.env.Process.TagArray = ao.env.Process.Tags + ao.env.Process.Tags = Tab(ao.env.Process) + -- magic table - if Content-Type == application/json - decode msg.Data to a Table + if msg.Tags['Content-Type'] and msg.Tags['Content-Type'] == 'application/json' then + msg.Data = require('json').decode(msg.Data or "{}") + end + -- init Errors + Errors = Errors or {} + -- clear Outbox + ao.clearOutbox() + + -- Only trust messages from a signed owner or an Authority + if msg.From ~= msg.Owner and not ao.isTrusted(msg) then + Send({Target = msg.From, Data = "Message is not trusted by this process!"}) + print('Message is not trusted! From: ' .. msg.From .. ' - Owner: ' .. msg.Owner) + return ao.result({ }) + end + + if ao.isAssignment(msg) and not ao.isAssignable(msg) then + Send({Target = msg.From, Data = "Assignment is not trusted by this process!"}) + print('Assignment is not trusted! From: ' .. msg.From .. ' - Owner: ' .. msg.Owner) + return ao.result({ }) + end + + Handlers.add("_eval", + function (msg) + return msg.Action == "Eval" and Owner == msg.From + end, + require('.eval')(ao) + ) + Handlers.append("_default", function () return true end, require('.default')(insertInbox)) + -- call evaluate from handlers passing env + msg.reply = + function(replyMsg) + replyMsg.Target = msg["Reply-To"] or (replyMsg.Target or msg.From) + replyMsg["X-Reference"] = msg["X-Reference"] or msg.Reference + replyMsg["X-Origin"] = msg["X-Origin"] or nil + + return ao.send(replyMsg) + end + + msg.forward = + function(target, forwardMsg) + -- Clone the message and add forwardMsg tags + local newMsg = ao.sanitize(msg) + forwardMsg = forwardMsg or {} + + for k,v in pairs(forwardMsg) do + newMsg[k] = v + end + + -- Set forward-specific tags + newMsg.Target = target + newMsg["Reply-To"] = msg["Reply-To"] or msg.From + newMsg["X-Reference"] = msg["X-Reference"] or msg.Reference + newMsg["X-Origin"] = msg["X-Origin"] or msg.From + -- clear functions + newMsg.reply = nil + newMsg.forward = nil + + ao.send(newMsg) + end + + local co = coroutine.create( + function() + return pcall(Handlers.evaluate, msg, ao.env) + end + ) + local _, status, result = coroutine.resume(co) + + -- Make sure we have a reference to the coroutine if it will wake up. + -- Simultaneously, prune any dead coroutines so that they can be + -- freed by the garbage collector. + table.insert(Handlers.coroutines, co) + for i, x in ipairs(Handlers.coroutines) do + if coroutine.status(x) == "dead" then + table.remove(Handlers.coroutines, i) + end + end + + if not status then + if (msg.Action == "Eval") then + table.insert(Errors, result) + local printData = table.concat(HANDLER_PRINT_LOGS, "\n") + return { Error = printData .. '\n\n' .. result } + end + --table.insert(Errors, result) + --ao.outbox.Output.data = "" + if msg.Action then + print(Colors.red .. "Error" .. Colors.gray .. " handling message with Action = " .. msg.Action .. Colors.reset) + else + print(Colors.red .. "Error" .. Colors.gray .. " handling message " .. Colors.reset) + end + print(Colors.green .. result .. Colors.reset) + print("\n" .. Colors.gray .. removeLastThreeLines(debug.traceback()) .. Colors.reset) + return ao.result({ Messages = {}, Spawns = {}, Assignments = {} }) + end + + if msg.Action == "Eval" then + local response = ao.result({ + Output = { + data = table.concat(HANDLER_PRINT_LOGS, "\n"), + prompt = Prompt(), + test = Dump(HANDLER_PRINT_LOGS) + } + }) + HANDLER_PRINT_LOGS = {} -- clear logs + return response + else + local response = ao.result({ Output = { data = table.concat(HANDLER_PRINT_LOGS, "\n"), prompt = Prompt(), print = true } }) + HANDLER_PRINT_LOGS = {} -- clear logs + return response + end +end + +return process diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.md b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.md new file mode 100644 index 0000000..105cf9a --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.md @@ -0,0 +1,87 @@ +# Lua Library: Process (Version 0.1.2) + +## Overview + +The Process library provides an environment for managing and executing processes on the AO network. It includes capabilities for handling messages, spawning processes, and customizing the environment with programmable logic and handlers. + +## Dependencies + +This module requires several external libraries: + +- `JSON`: For handling JSON data. +- `pretty`: For pretty-printing tables. +- `base64`: For encoding and decoding base64 strings. +- `ao`: For managing AO-specific operations like sending messages. +- `handlers`: For managing and executing custom handler functions. + +## Module Structure + +- `process._version`: String representing the version of the Process library. +- `manpages`: A table storing manual pages for different functions or modules. + +## Functions + +### `prompt()` + +Returns a custom command prompt string. + +#### Returns + +- `string`: The command prompt. + +### `initializeState(msg, env)` + +Initializes or updates the state of the process based on the incoming message and environment. + +#### Parameters + +- `msg` (table): The incoming message. +- `env` (table): The environment in which the process is operating. + +### `version()` + +Prints the version of the Process library. + +### `man(page)` + +Returns the manual page for a given topic. + +#### Parameters + +- `page` (string, optional): The name of the manual page. + +#### Returns + +- `string`: The content of the manual page. + +### `process.handle(msg, env)` + +Main handler for processing incoming messages. It initializes the state, processes commands, and handles message evaluation and inbox management. + +#### Parameters + +- `msg` (table): The message to be handled. +- `env` (table): The environment of the process. + +#### Returns + +- Varies: The response based on the processed message. + +## Usage Example + +```lua +local process = require "process_module_path" + +-- Example message and environment +local msg = { /* message structure */ } +local env = { /* environment structure */ } + +-- Handle a message +local response = process.handle(msg, env) +``` + +## Notes + +- The `process.handle` function is the core of this module, determining how messages are processed, including evaluating expressions, managing manual pages, and calling custom handlers. +- This module is designed to be used within the AO network environment, leveraging the AO and Handlers libraries for communication and process management. +- Manual pages (`manpages`) provide documentation and guidance for users of the aos system. diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/repl.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/repl.js new file mode 100644 index 0000000..86da13b --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/repl.js @@ -0,0 +1,81 @@ +const readline = require("readline"); +const AoLoader = require("@permaweb/ao-loader"); +const fs = require("fs"); +const wasm = fs.readFileSync("./process.wasm"); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +const env = { + Process: { + Id: "PROCESS_TEST", + Owner: "TOM", + }, +}; +let prompt = "aos"; + +async function repl(state) { + const handle = await AoLoader(wasm); + + rl.question(prompt + "> ", async function (line) { + // Exit the REPL if the user types "exit" + if (line === "exit") { + console.log("Exiting..."); + rl.close(); + return; + } + let response = {}; + // Evaluate the JavaScript code and print the result + try { + const message = createMessage(line); + response = handle(state, message, env); + console.log(response.Output); + if (response.Output.data.output) { + console.log(response.Output.data.output); + } + //console.log(response.messages) + if (response.Output.data.prompt) { + prompt = response.Output.data.prompt; + } + + // Continue the REPL + await repl(response.buffer); + } catch (err) { + console.log("Error:", err); + process.exit(0); + } + }); +} + +repl(null); + +function createMessage(expr) { + return { + Owner: "TOM", + Target: "PROCESS", + Tags: [ + { name: "Data-Protocol", value: "ao" }, + { name: "Variant", value: "ao.TN.1" }, + { name: "Type", value: "message" }, + { name: "function", value: "eval" }, + { name: "expression", value: expr }, + ], + }; +} + +/** + * const spawn = { + owner: "TOM", + tags: [ + + { name: "Data-Protocol", value: "ao" }, + { name: "ao-type", value: "spawn" }, + { name: "function", value: "eval" }, + { name: "expression", value: expr } + + ] +} + + */ diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/stringify.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/stringify.lua new file mode 100644 index 0000000..3288030 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/stringify.lua @@ -0,0 +1,79 @@ +local stringify = { _version = "0.0.1" } + +-- ANSI color codes +local colors = { + red = "\27[31m", + green = "\27[32m", + blue = "\27[34m", + reset = "\27[0m" +} + +function stringify.isSimpleArray(tbl) + local arrayIndex = 1 + for k, v in pairs(tbl) do + if k ~= arrayIndex or (type(v) ~= "number" and type(v) ~= "string") then + return false + end + arrayIndex = arrayIndex + 1 + end + return true +end + +function stringify.format(tbl, indent) + indent = indent or 0 + local toIndent = string.rep(" ", indent) + local toIndentChild = string.rep(" ", indent + 2) + + local result = {} + local isArray = true + local arrayIndex = 1 + + if stringify.isSimpleArray(tbl) then + for _, v in ipairs(tbl) do + if type(v) == "string" then + v = colors.green .. '"' .. v .. '"' .. colors.reset + else + v = colors.blue .. tostring(v) .. colors.reset + end + table.insert(result, v) + end + return "{ " .. table.concat(result, ", ") .. " }" + end + + for k, v in pairs(tbl) do + if isArray then + if k == arrayIndex then + arrayIndex = arrayIndex + 1 + if type(v) == "table" then + v = stringify.format(v, indent + 2) + elseif type(v) == "string" then + v = colors.green .. '"' .. v .. '"' .. colors.reset + else + v = colors.blue .. tostring(v) .. colors.reset + end + table.insert(result, toIndentChild .. v) + else + isArray = false + result = {} + end + end + if not isArray then + if type(v) == "table" then + v = stringify.format(v, indent + 2) + elseif type(v) == "string" then + v = colors.green .. '"' .. v .. '"' .. colors.reset + else + v = colors.blue .. tostring(v) .. colors.reset + end + k = colors.red .. k .. colors.reset + table.insert(result, toIndentChild .. k .. " = " .. v) + end + end + + local prefix = isArray and "{\n" or "{\n " + local suffix = isArray and "\n" .. toIndent .. " }" or "\n" .. toIndent .. "}" + local separator = isArray and ",\n" or ",\n " + return prefix .. table.concat(result, separator) .. suffix +end + +return stringify diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/assignment.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/assignment.test.js new file mode 100644 index 0000000..f30f0bd --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/assignment.test.js @@ -0,0 +1,477 @@ +import { describe, test } from "node:test"; +import * as assert from "node:assert"; +import fs from "node:fs"; + +import AoLoader from "@permaweb/ao-loader"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; + +const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, +}; + +describe("add the assignable MatchSpec", async () => { + test("by name", async () => { + const handle = await AoLoader(wasm, options); + + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Thomas" }, + ], + Data: ` + ao.addAssignable('foobar', function (msg) return true end) + Send({ Target = "TEST", Data = { length = #ao.assignables, name = ao.assignables[1].name } }) + `, + }; + + const result = await handle(null, msg, env); + + assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { + length: 1, + name: "foobar", + }); + }); + + test("update by name", async () => { + const handle = await AoLoader(wasm, options); + + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Thomas" }, + ], + Data: ` + ao.addAssignable('foobar', function (msg) return true end) + ao.addAssignable('foobar', function (msg) return false end) + Send({ Target = "TEST", Data = { length = #ao.assignables, name = ao.assignables[1].name } }) + `, + }; + + const result = await handle(null, msg, env); + + assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { + length: 1, + name: "foobar", + }); + }); + + test("by index", async () => { + const handle = await AoLoader(wasm, options); + + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Thomas" }, + ], + Data: ` + ao.addAssignable(function (msg) return true end) + Send({ Target = "TEST", Data = { length = #ao.assignables, name = ao.assignables[1].name } }) + `, + }; + + const result = await handle(null, msg, env); + + assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { length: 1 }); + }); + + test("require name to be a string", async () => { + const handle = await AoLoader(wasm, options); + + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Thomas" }, + ], + Data: ` + ao.addAssignable(1234, function (msg) return true end) + `, + }; + + const result = await handle(null, msg, env); + + assert.ok(result.Error.includes("MatchSpec name MUST be a string")); + }); +}); + +describe("remove the assignable MatchSpec", () => { + test("by name", async () => { + const handle = await AoLoader(wasm, options); + + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Thomas" }, + ], + Data: ` + ao.addAssignable(function (msg) return true end) + ao.addAssignable('foobar', function (msg) return true end) + Send({ Target = "TEST", Data = { length = #ao.assignables, name = ao.assignables[2].name } }) + + ao.removeAssignable('foobar') + Send({ Target = "TEST", Data = { length = #ao.assignables } }) + `, + }; + + const result = await handle(null, msg, env); + + assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { + length: 2, + name: "foobar", + }); + assert.deepStrictEqual(JSON.parse(result.Messages[1].Data), { length: 1 }); + }); + + test("by index", async () => { + const handle = await AoLoader(wasm, options); + + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Thomas" }, + ], + Data: ` + ao.addAssignable(function (msg) return true end) + ao.addAssignable('foobar', function (msg) return true end) + Send({ Target = "TEST", Data = { length = #ao.assignables, name = ao.assignables[2].name } }) + + ao.removeAssignable(1) + Send({ Target = "TEST", Data = { length = #ao.assignables, name = ao.assignables[1].name } }) + `, + }; + + const result = await handle(null, msg, env); + + assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { + length: 2, + name: "foobar", + }); + assert.deepStrictEqual(JSON.parse(result.Messages[1].Data), { + length: 1, + name: "foobar", + }); + }); + + test("require name to be a string or number", async () => { + const handle = await AoLoader(wasm, options); + + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Thomas" }, + ], + Data: ` + ao.removeAssignable({}) + `, + }; + + const result = await handle(null, msg, env); + assert.ok(result.Error.includes("index MUST be a number")); + }); +}); + +describe("determine whether the msg is an assignment or not", () => { + test("is an assignment", async () => { + const handle = await AoLoader(wasm, options); + + const addAssignableMsg = { + Target: "AOS", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Thomas" }, + ], + Data: ` + ao.addAssignable(function (msg) return true end) + Handlers.add( + "IsAssignment", + function (Msg) return Msg.Action == 'IsAssignment' end, + function (Msg) + Send({ Target = 'TEST', Data = { id = Msg.Id, isAssignment = ao.isAssignment(Msg) } }) + end + ) + `, + }; + + const { Memory } = await handle(null, addAssignableMsg, env); + + const msg = { + Target: "NOT_AOS", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "IsAssignment" }, + { name: "Name", value: "Thomas" }, + ], + Data: "foobar", + }; + + const result = await handle(Memory, msg, env); + assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { + id: "1234xyxfoo", + isAssignment: true, + }); + }); + + test("is NOT an assignment", async () => { + const handle = await AoLoader(wasm, options); + + const addAssignableMsg = { + Target: "AOS", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Thomas" }, + ], + Data: ` + ao.addAssignable(function (msg) return true end) + Handlers.add( + "IsAssignment", + function (Msg) return Msg.Action == 'IsAssignment' end, + function (Msg) + Send({ Target = 'TEST', Data = { id = Msg.Id, isAssignment = ao.isAssignment(Msg) } }) + end + ) + `, + }; + + const { Memory } = await handle(null, addAssignableMsg, env); + + const msg = { + Target: "AOS", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "IsAssignment" }, + { name: "Name", value: "Thomas" }, + ], + Data: "foobar", + }; + + const result = await handle(Memory, msg, env); + + assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { + id: "1234xyxfoo", + isAssignment: false, + }); + }); +}); + +describe("run handles on assignment based on assignables configured", () => { + test("at least 1 assignable allows specific assignment", async () => { + const handle = await AoLoader(wasm, options); + + const addAssignableMsg = { + Target: "AOS", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Thomas" }, + ], + Data: ` + ao.addAssignable(function (msg) return msg.Name == 'Frank' end) + ao.addAssignable(function (msg) return msg.Name == 'Thomas' end) + `, + }; + + const { Memory } = await handle(null, addAssignableMsg, env); + + const msg = { + Target: "NOT_AOS", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Thomas" }, + ], + Data: "2 + 2", + }; + + const result = await handle(Memory, msg, env); + assert.deepStrictEqual(result.Output.data, "4"); + }); + + test("assignables do NOT allow specific assignment", async () => { + const handle = await AoLoader(wasm, options); + + const addAssignableMsg = { + Target: "AOS", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Thomas" }, + ], + Data: ` + ao.addAssignable(function (msg) return msg.Name == 'Frank' end) + ao.addAssignable(function (msg) return msg.Name == 'Thomas' end) + `, + }; + + const { Memory } = await handle(null, addAssignableMsg, env); + + const msg = { + Target: "NOT_AOS", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Not-Thomas" }, + ], + Data: "2 + 2", + }; + + const result = await handle(Memory, msg, env); + assert.deepStrictEqual( + result.Messages[0].Data, + "Assignment is not trusted by this process!", + ); + }); + + test("assignable does NOT allow specific assignment", async () => { + const handle = await AoLoader(wasm, options); + + const addAssignableMsg = { + Target: "AOS", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Thomas" }, + ], + Data: ` + ao.addAssignable(function (msg) return msg.Name == 'Thomas' end) + `, + }; + + const { Memory } = await handle(null, addAssignableMsg, env); + + const msg = { + Target: "NOT_AOS", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Not-Thomas" }, + ], + Data: "2 + 2", + }; + + const result = await handle(Memory, msg, env); + assert.deepStrictEqual( + result.Messages[0].Data, + "Assignment is not trusted by this process!", + ); + }); + + test("no assignables defaults to no assignments allowed", async () => { + const handle = await AoLoader(wasm, options); + + const msg = { + Target: "NOT_AOS", + Owner: "FOOBAR", + "Block-Height": "1000", + Id: "1234xyxfoo", + From: "FOOBAR", + Module: "WOOPAWOOPA", + Tags: [ + { name: "Action", value: "Eval" }, + { name: "Name", value: "Not-Thomas" }, + ], + Data: "2 + 2", + }; + + const result = await handle(null, msg, env); + assert.deepStrictEqual( + result.Messages[0].Data, + "Assignment is not trusted by this process!", + ); + }); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/aes.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/aes.test.js new file mode 100644 index 0000000..82719a6 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/aes.test.js @@ -0,0 +1,95 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { + format: "wasm64-unknown-emscripten-draft_2024_02_15", + computeLimit: 10024704733, +}; + +test("run aes cipher successfully", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + + const results = [ + // AES128 CBC Mode + "A3B9E6E1FBD9D46930E5F76807C84B8E", + "616F0000000000000000000000000000", + // AES128 ECB Mode + "3FF54BD61AD1AA06BC367A10575CC7C5", + "616F0000000000000000000000000000", + // AES128 CFB Mode + "1DA7169C093D6B23160B6785B28E4BED", + "616F0000000000000000000000000000", + // AES128 OFB Mode + "1DA7169C093D6B23160B6785B28E4BED", + "616F0000000000000000000000000000", + // AES128 CTR Mode + "1DA7169C093D6B23160B6785B28E4BED", + "616F0000000000000000000000000000", + ]; + + const data = ` + local crypto = require(".crypto") + local Hex = require(".crypto.util.hex") + + local modes = { "CBC", "ECB", "CFB", "OFB", "CTR" } + local iv = "super_secret_shh" + + local key_128 = "super_secret_shh" + local key_192 = "super_secret_password_sh" + local key_256 = "super_duper_secret_password_shhh" + local results = {} + + local run = function(keySize, modes) + for _, mode in ipairs(modes) do + local key = key_128 + if keySize == 192 then + key = key_192 + elseif keySize == 256 then + key = key_256 + end + + + local l_iv = iv + if mode == "ECB" then + l_iv = "" + end + + local encrypted = crypto.cipher.aes.encrypt("ao", key, l_iv, mode, keySize).asHex() + local decrypted = crypto.cipher.aes.decrypt(encrypted, key, l_iv, mode, keySize).asHex() + results[#results + 1] = encrypted + results[#results + 1] = decrypted + end + end + + run(128, modes) + -- run(192, modes) + -- run(256, modes) + return table.concat(results, ", ") + `; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: data, + }; + + const result = await handle(null, msg, env); + + assert.equal(result.Output?.data, results.join(", ")); + // assert.ok(result.GasUsed >= 3000000000) + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/issac.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/issac.test.js new file mode 100644 index 0000000..6beea27 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/issac.test.js @@ -0,0 +1,52 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +test("run issac cipher successfully", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + + const results = ["7851", "ao"]; + + const data = ` + local crypto = require(".crypto"); + + local results = {}; + + local message = "ao"; + local key = "secret_key"; + + local encrypted, decrypted; + + encrypted = crypto.cipher.issac.encrypt(message, key); + decrypted = crypto.cipher.issac.decrypt(encrypted.asString(), key); + + results[1] = encrypted.asHex(); + results[2] = decrypted; + + return table.concat(results, ", "); + `; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: data, + }; + + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, results.join(", ")); + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/morus.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/morus.test.js new file mode 100644 index 0000000..90ffc7e --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/morus.test.js @@ -0,0 +1,71 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); + +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +test("run morus cipher successfully", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + + const results = [ + "514ed31473d8fb0b76c6cbb17af35ed01d0a", + "ao", + "6164646974696f6e616c20646174616aae7a8b95c50047bea251c3b7133eec5fcc", + "ao", + ]; + + const data = ` + local crypto = require(".crypto"); + + local results = {}; + + local m = "ao"; + + --[[ + 16 bit key + ]]-- + + local k = "super_secret_shh" + local iv = "0000000000000000" + local ad= ""; + + local e = crypto.cipher.morus.encrypt(k, iv, m, ad); + results[1] = e.asHex(); + results[2] = crypto.cipher.morus.decrypt(k, iv, e.asString(), #ad); + + --[[ + 32 bit key + ]]-- + k = crypto.utils.hex.hexToString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); + ad = "additional data"; + + e = crypto.cipher.morus.encrypt(k, iv, m, ad); + results[3] = e.asHex(); + results[4] = crypto.cipher.morus.decrypt(k, iv, e.asString(), #ad); + + return table.concat(results, ", "); + `; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: data, + }; + + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, results.join(", ")); + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/norx.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/norx.test.js new file mode 100644 index 0000000..b5d5cb1 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/norx.test.js @@ -0,0 +1,69 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); + +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +test("run norx cipher successfully", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + + const results = [ + // encrypted cipher + "0bb35a06938e6541eccd4440adb7b46118535f60b09b4adf378807a53df19fc4ea28", + // auth tag + "5a06938e6541eccd4440adb7b46118535f60b09b4adf378807a53df19fc4ea28", + // decrypted value + "ao", + ]; + + const data = ` + local crypto = require(".crypto"); + local Hex = require(".crypto.util.hex") + + local results = {} + + -- nonce and key are 32 bytes each + local key = "super_duper_secret_password_shhh" + local nonce = "00000000000000000000000000000000" + + -- Data to encrypt + local data = "ao" + + -- Header and trailer are optional + local header, trailer = data, data + + local encrypted = crypto.cipher.norx.encrypt(key, nonce, data, header, trailer).asString() + local decrypted = crypto.cipher.norx.decrypt(key, nonce, encrypted, header, trailer) + + local authTag = encrypted:sub(#encrypted-32+1) + + results[1] = Hex.stringToHex(encrypted) + results[2] = Hex.stringToHex(authTag) + results[3] = decrypted + + return table.concat(results, ", ") + `; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: data, + }; + + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, results.join(", ")); + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/blake2b.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/blake2b.test.js new file mode 100644 index 0000000..bd1f111 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/blake2b.test.js @@ -0,0 +1,50 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +test("run sha3 hash successfully", async () => { + const results = [ + "576701fd79a126f2c414ef94adf1117c88943700f312679d018c29c378b2c807a3412b4e8d51e191c48fb5f5f54bf1bca29a714dda166797b3baf9ead862ae1d", + "7050811afc947ba7190bb3c0a7b79b4fba304a0de61d529c8a35bdcbbb5544f4", + "203c101980fdf6cf24d78879f2e3db86d73d91f7d60960b642022cd6f87408f8", + ]; + + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + + const data = ` + local crypto = require(".crypto"); + + local results = {}; + + results[1] = crypto.digest.blake2b("ao").asHex(); + results[2] = crypto.digest.blake2b("ao", 32).asHex(); + results[3] = crypto.digest.blake2b("ao", 32, "secret_key").asHex(); + + + return table.concat(results, ", "); + `; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: data, + }; + + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, results.join(", ")); + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md2.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md2.test.js new file mode 100644 index 0000000..46aad07 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md2.test.js @@ -0,0 +1,55 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); + +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; + +test("run md2 hash successfully", async () => { + const cases = [ + ["", "8350e5a3e24c153df2275c9f80692773"], + ["ao", "0d4e80edd07bee6c7965b21b25a9b1ea"], + ["abc", "da853b0d3f88d99b30283a69e6ded6bb"], + ["abcdefghijklmnopqrstuvwxyz", "4e8ddff3650292ab5a4108c3aa47940b"], + ["Hello World!", "315f7c67223f01fb7cab4b95100e872e"], + ]; + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const testCase = async (e) => { + const data = ` + local crypto = require(".crypto"); + + local str = crypto.utils.stream.fromString("${e[0]}"); + return crypto.digest.md2(str).asHex(); + `; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: data, + }; + + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, e[1]); + assert.ok(true); + }; + await testCase(cases[0]); + await testCase(cases[1]); + await testCase(cases[2]); + await testCase(cases[3]); + await testCase(cases[4]); + + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md4.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md4.test.js new file mode 100644 index 0000000..26b4945 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md4.test.js @@ -0,0 +1,53 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); + +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +test("run md4 hash successfully", async () => { + const cases = [ + ["", "31d6cfe0d16ae931b73c59d7e0c089c0"], + ["ao", "e068dfe3d8cb95311b58be566db66954"], + ["abc", "a448017aaf21d8525fc10ae87aa6729d"], + ["abcdefghijklmnopqrstuvwxyz", "d79e1c308aa5bbcdeea8ed63df412da9"], + ["Hello World!", "b2a5cc34fc21a764ae2fad94d56fadf6"], + ]; + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + + const testCase = async (e) => { + const data = ` + local crypto = require(".crypto"); + + local str = crypto.utils.stream.fromString("${e[0]}"); + return crypto.digest.md4(str).asHex(); + `; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: data, + }; + + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, e[1]); + assert.ok(true); + }; + await testCase(cases[0]); + await testCase(cases[1]); + await testCase(cases[2]); + await testCase(cases[3]); + await testCase(cases[4]); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md5.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md5.test.js new file mode 100644 index 0000000..a7f4e71 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md5.test.js @@ -0,0 +1,53 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; + +test("run md5 hash successfully", async () => { + const cases = [ + ["", "d41d8cd98f00b204e9800998ecf8427e"], + ["ao", "adac5e63f80f8629e9573527b25891d3"], + ["abc", "900150983cd24fb0d6963f7d28e17f72"], + ["abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b"], + ["Hello World!", "ed076287532e86365e841e92bfc50d8c"], + ]; + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + + const testCase = async (e) => { + const data = ` + local crypto = require(".crypto"); + + local str = crypto.utils.stream.fromString("${e[0]}"); + return crypto.digest.md5(str).asHex(); + `; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: data, + }; + + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, e[1]); + assert.ok(true); + }; + await testCase(cases[0]); + await testCase(cases[1]); + await testCase(cases[2]); + await testCase(cases[3]); + await testCase(cases[4]); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha1.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha1.test.js new file mode 100644 index 0000000..0f2f69a --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha1.test.js @@ -0,0 +1,52 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +test("run sha1 hash successfully", async () => { + const cases = [ + ["", "da39a3ee5e6b4b0d3255bfef95601890afd80709"], + ["ao", "c29dd6c83b67a1d6d3b28588a1f068b68689aa1d"], + ["abc", "a9993e364706816aba3e25717850c26c9cd0d89d"], + ["abcdefghijklmnopqrstuvwxyz", "32d10c7b8cf96570ca04ce37f2a19d84240d3a89"], + ["Hello World!", "2ef7bde608ce5404e97d5f042f95f89f1c232871"], + ]; + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + + const testCase = async (e) => { + const data = ` + local crypto = require(".crypto"); + + local str = crypto.utils.stream.fromString("${e[0]}"); + return crypto.digest.sha1(str).asHex(); + `; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: data, + }; + + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, e[1]); + assert.ok(true); + }; + await testCase(cases[0]); + await testCase(cases[1]); + await testCase(cases[2]); + await testCase(cases[3]); + await testCase(cases[4]); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha2.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha2.test.js new file mode 100644 index 0000000..d295bcd --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha2.test.js @@ -0,0 +1,50 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +test("run sha2 hash successfully", async () => { + const results = [ + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", + "6f36a696b17ce5a71efa700e8a7e47994f3e134a5e5f387b3e7c2c912abe94f94ee823f9b9dcae59af99e2e34c8b4fb0bd592260c6720ee49e5deaac2065c4b1", + ]; + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + + const data = ` + local crypto = require(".crypto"); + + local results = {}; + + local data1 = crypto.utils.stream.fromString("abc"); + local data2 = "ao" + + + results[1] = crypto.digest.sha2_256(data1).asHex(); + results[2] = crypto.digest.sha2_512(data2).asHex(); + + return table.concat(results, ", "); + `; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: data, + }; + + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, results.join(", ")); + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha3.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha3.test.js new file mode 100644 index 0000000..e49cb29 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha3.test.js @@ -0,0 +1,51 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +test("run sha3 hash successfully", async () => { + const results = [ + "1bbe785577db997a394d5b4555eec9159cb51f235aec07514872d2d436c6e985", + "0c29f053400cb1764ce2ec555f598f497e6fcd1d304ce0125faa03bb724f63f213538f41103072ff62ddee701b52c73e621ed4d2254a3e5e9a803d83435b704d", + "76da52eec05b749b99d6e62bb52333c1569fe75284e6c82f3de12a4618be00d6", + "046fbfad009a12cef9ff00c2aac361d004347b2991c1fa80fba5582251b8e0be8def0283f45f020d4b04ff03ead9f6e7c43cc3920810c05b33b4873b99affdea", + ]; + + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + + const data = ` + local crypto = require(".crypto"); + + local results = {}; + + results[1] = crypto.digest.sha3_256("ao").asHex(); + results[2] = crypto.digest.sha3_512("ao").asHex(); + results[3] = crypto.digest.keccak256("ao").asHex(); + results[4] = crypto.digest.keccak512("ao").asHex(); + + return table.concat(results,", ") + `; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: data, + }; + + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, results.join(", ")); + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/kdf/pbkdf2.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/kdf/pbkdf2.test.js new file mode 100644 index 0000000..5ccaa74 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/kdf/pbkdf2.test.js @@ -0,0 +1,48 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; + +test("run pbkdf2 successfully", async () => { + const results = ["C4C21BF2BBF61541408EC2A49C89B9C6"]; + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + + const data = ` + local crypto = require(".crypto"); + + local results = {}; + + local salt = crypto.utils.array.fromString("salt") + local password = crypto.utils.array.fromString("password") + local iterations = 4 + local keyLen = 16 + + local out = crypto.kdf.pbkdf2(password, salt, iterations, keyLen) + + return out.asHex() + `; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: data, + }; + + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, results); + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/mac/hmac.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/mac/hmac.test.js new file mode 100644 index 0000000..fa2f733 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/mac/hmac.test.js @@ -0,0 +1,50 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +test("run hmac successfully", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + + const results = [ + "3966f45acb53f7a1a493bae15afecb1a204fa32d", + "542da02a324155d688c7689669ff94c6a5f906892aa8eccd7284f210ac66e2a7", + ]; + + const data = ` + local crypto = require(".crypto") + + local data = crypto.utils.stream.fromString("ao") + local key = crypto.utils.array.fromString("super_secret_key") + + local results = {} + + results[1] = crypto.mac.createHmac(data, key).asHex() + results[2] = crypto.mac.createHmac(data, key, "sha256").asHex() + + return table.concat(results, ", ") + `; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: data, + }; + + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, results.join(", ")); + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/random.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/random.test.js new file mode 100644 index 0000000..c60a11a --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/random.test.js @@ -0,0 +1,37 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +test("run random generator successfully", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + + const data = ` + local crypto = require(".crypto") + + return crypto.random(); + `; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: data, + }; + + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, 532713800); + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/eval.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/eval.test.js new file mode 100644 index 0000000..9bc130d --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/eval.test.js @@ -0,0 +1,107 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +test("run evaluate action unsuccessfully", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: "100 < undefined", + }; + const result = await handle(null, msg, env); + + assert.ok(result.Error.includes("attempt to compare number with nil")); + assert.ok(true); +}); + +test("run evaluate action successfully", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: "1 + 1", + }; + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, "2"); + assert.ok(true); +}); + +test("print hello world", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: `print("Hello World")`, + }; + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, "Hello World"); + assert.ok(true); +}); + +test("create an Assignment", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: 'Assign({ Processes = { "pid-1", "pid-2" }, Message = "mid-1" })', + }; + const result = await handle(null, msg, env); + + assert.deepStrictEqual(result.Assignments, [ + { Processes: ["pid-1", "pid-2"], Message: "mid-1" }, + ]); + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/handlers.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/handlers.test.js new file mode 100644 index 0000000..7d765bf --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/handlers.test.js @@ -0,0 +1,332 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; + +test("handlers receive", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: ` +local msg = ao.send({Target = ao.id, Data = "Hello"}) +local res = Handlers.receive({From = msg.Target, ['X-Reference'] = msg.Ref_}) +print('received msg') +return require('json').encode(res) + `, + }; + + // load handler + const { Memory, Output, Messages } = await handle(null, msg, env); + //console.log(Output) + console.log(Messages[0]); + // --- + const m = { + Target: "AOS", + From: "FRED", + Owner: "FRED", + Tags: [ + { + name: "X-Reference", + value: "1", + }, + ], + Data: "test receive", + }; + const result = await handle(Memory, m, env); + console.log(result.Output); + assert.ok(true); +}); + +test("resolvers", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: ` +Handlers.once("onetime", + { + Action = "ping", + Data = "ping" + }, + function (Msg) + print("pong") + end +) + `, + }; + // load handler + const { Memory } = await handle(null, msg, env); + // --- + const ping = { + Target: "AOS", + From: "FRED", + Owner: "FRED", + Tags: [{ name: "Action", value: "ping" }], + Data: "ping", + }; + const result = await handle(Memory, ping, env); + // handled once + assert.equal(result.Output.data, "pong"); +}); + +test("handlers once", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: ` +Handlers.once("onetime", + Handlers.utils.hasMatchingData("ping"), + function (Msg) + print("pong") + end +) + `, + }; + // load handler + const { Memory } = await handle(null, msg, env); + // --- + const ping = { + From: "FRED", + Target: "AOS", + Owner: "FRED", + Tags: [], + Data: "ping", + }; + const result = await handle(Memory, ping, env); + // handled once + assert.equal(result.Output.data, "pong"); + + const result2 = await handle(result.Memory, ping, env); + // not handled + assert.ok(result2.Output.data.includes("New Message From")); +}); + +test("ping pong", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: ` +Handlers.add("ping", + Handlers.utils.hasMatchingData("ping"), + function (Msg) + print("pong") + end +) + `, + }; + // load handler + const { Memory } = await handle(null, msg, env); + // --- + const ping = { + Target: "AOS", + From: "FRED", + Owner: "FRED", + Tags: [], + Data: "ping", + }; + const result = await handle(Memory, ping, env); + assert.equal(result.Output.data, "pong"); + assert.ok(true); +}); + +test("handler pipeline", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: ` +Handlers.add("one", + function (Msg) + return "continue" + end, + function (Msg) + print("one") + end +) +Handlers.add("two", + function (Msg) + return "continue" + end, + function (Msg) + print("two") + end +) + +Handlers.add("three", + function (Msg) + return "skip" + end, + function (Msg) + print("three") + end +) + `, + }; + // load handler + const { Memory } = await handle(null, msg, env); + // --- + const ping = { + Target: "AOS", + From: "FRED", + Owner: "FRED", + Tags: [], + Data: "ping", + }; + const result = await handle(Memory, ping, env); + assert.equal( + result.Output.data, + "one\ntwo\n\x1B[90mNew Message From \x1B[32mFRE...RED\x1B[90m: \x1B[90mData = \x1B[34mping\x1B[0m", + ); + assert.ok(true); +}); + +test("timestamp", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: ` +Handlers.add("timestamp", + Handlers.utils.hasMatchingData("timestamp"), + function (Msg) + print(os.time()) + end +) + `, + }; + // load handler + const { Memory } = await handle(null, msg, env); + // --- + const currentTimestamp = Date.now(); + const timestamp = { + Target: "AOS", + From: "FRED", + Owner: "FRED", + Tags: [], + Data: "timestamp", + Timestamp: currentTimestamp, + }; + const result = await handle(Memory, timestamp, env); + assert.equal(result.Output.data, currentTimestamp); + assert.ok(true); +}); + +test("test pattern, fn handler", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: ` +Handlers.add("Balance", + function (msg) + msg.reply({Data = "1000"}) + end +) + `, + }; + // load handler + const { Memory } = await handle(null, msg, env); + // --- + const currentTimestamp = Date.now(); + const balance = { + Target: "AOS", + From: "FRED", + Owner: "FRED", + Tags: [{ name: "Action", value: "Balance" }], + Data: "timestamp", + Timestamp: currentTimestamp, + }; + const result = await handle(Memory, balance, env); + assert.equal(result.Messages[0].Data, "1000"); + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/inbox.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/inbox.test.js new file mode 100644 index 0000000..448b37e --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/inbox.test.js @@ -0,0 +1,51 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; + +test.skip("inbox unbounded", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Data: "Hello", + Tags: [], + }; + const result = await handle(null, msg, env); + let memory = result.Memory; + for (var i = 0; i < 10001; i++) { + const { Memory } = await handle(memory, msg, env); + memory = Memory; + } + const count = await handle( + memory, + { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: "#Inbox", + }, + env, + ); + //assert.equal(count.Error, 'Error') + assert.equal(count.Output?.data, "10000"); + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/magic-table.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/magic-table.test.js new file mode 100644 index 0000000..418ea4a --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/magic-table.test.js @@ -0,0 +1,65 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; + +test("magictable to wrap send to convert data to json", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: 'Send({ Target = "AOS", Data = { foo = "bar" }})', + }; + const result = await handle(null, msg, env); + assert.equal(result.Messages[0].Data, '{"foo":"bar"}'); + const msg2 = Object.assign({}, msg, result.Messages[0]); + const tableResult = await handle(result.Memory, msg2, env); + const inboxResult = await handle( + tableResult.Memory, + Object.assign({}, msg, { + Tags: [{ name: "Action", value: "Eval" }], + Data: "Inbox[1].Data.foo", + }), + env, + ); + assert.equal(inboxResult.Output.data, "bar"); +}); + +test("magictable to wrap swap to convert data to json", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: 'Spawn("AWESOME_SAUCE", { Target = "TEST", Data = { foo = "bar" }})', + }; + const result = await handle(null, msg, env); + assert.equal(result.Spawns[0].Data, '{"foo":"bar"}'); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/print.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/print.test.js new file mode 100644 index 0000000..7c34a71 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/print.test.js @@ -0,0 +1,124 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; + +test("multi print feature", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: ` +print("one") +print("two") +`, + }; + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, "one\ntwo"); + assert.ok(true); +}); + +test("multi print feature via handler", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: 'Handlers.add("ping", Handlers.utils.hasMatchingData("ping"), function (m) print(m.Data); print("pong") end)', + }; + const { Memory } = await handle(null, msg, env); + let msg2 = msg; + msg2.Tags = []; + msg2.Data = "ping"; + const result = await handle(Memory, msg2, env); + assert.equal(result.Output.data, "ping\npong"); + assert.ok(true); +}); + +test("Typos for functions should generate errors", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: 'Handers.add("ping", Handlers.utils.hasMatchingData("ping"), function (m) print(m.Data); print("pong") end)', + }; + const { Memory, Output, Error } = await handle(null, msg, env); + + let msg2 = msg; + msg2.Tags = [{ name: "Action", value: "Eval" }]; + msg2.Data = "Errors"; + const result = await handle(Memory, msg2, env); + assert.ok( + result.Output.data.includes( + "attempt to index a nil value (global 'Handers')", + ), + ); +}); + +test("Print Errors in Handlers", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: 'Handlers.add("ping", Handlers.utils.hasMatchingData("ping"), function (m) print(m.Data); print("pong" .. x) end)', + }; + const { Memory, Output, Error } = await handle(null, msg, env); + + let msg2 = msg; + msg2.Tags = []; + msg2.Data = "ping"; + const result = await handle(Memory, msg2, env); + + assert.ok(result.Output.data.includes("handling message")); + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/random.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/random.test.js new file mode 100644 index 0000000..7e4a9af --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/random.test.js @@ -0,0 +1,31 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; + +test("generate random number", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: "math.random(10)", + }; + const result = await handle(null, msg, env); + assert.equal(result.Output?.data, "9"); + assert.ok(true); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/state.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/state.test.js new file mode 100644 index 0000000..0b82172 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/state.test.js @@ -0,0 +1,63 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import AoLoader from "@permaweb/ao-loader"; +import fs from "fs"; + +const wasm = fs.readFileSync("./process.wasm"); +const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; + +test("check state properties for aos", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [{ name: "Name", value: "Thomas" }], + }, + }; + const msg = { + Target: "AOS", + From: "FOOBAR", + Owner: "FOOBAR", + From: "FOOBAR", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: 'print("name: " .. Name .. ", owner: " .. Owner)', + }; + const result = await handle(null, msg, env); + + assert.equal(result.Output?.data, "name: Thomas, owner: FOOBAR"); + assert.ok(true); +}); + +test("test authorities", async () => { + const handle = await AoLoader(wasm, options); + const env = { + Process: { + Id: "AOS", + Owner: "FOOBAR", + Tags: [ + { name: "Name", value: "Thomas" }, + { name: "Authority", value: "BOOP" }, + ], + }, + }; + const msg = { + Target: "AOS", + Owner: "BEEP", + From: "BAM", + ["Block-Height"]: "1000", + Id: "1234xyxfoo", + Module: "WOOPAWOOPA", + Tags: [{ name: "Action", value: "Eval" }], + Data: "1 + 1", + }; + const result = await handle(null, msg, env); + assert.ok( + result.Output.data.includes( + "Message is not trusted! From: BAM - Owner: BEEP", + ), + ); +}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.lua new file mode 100644 index 0000000..8f30cde --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.lua @@ -0,0 +1,261 @@ +local utils = { _version = "0.0.5" } + +function utils.matchesPattern(pattern, value, msg) + -- If the key is not in the message, then it does not match + if(not pattern) then + return false + end + -- if the patternMatchSpec is a wildcard, then it always matches + if pattern == '_' then + return true + end + -- if the patternMatchSpec is a function, then it is executed on the tag value + if type(pattern) == "function" then + if pattern(value, msg) then + return true + else + return false + end + end + + -- if the patternMatchSpec is a string, check it for special symbols (less `-` alone) + -- and exact string match mode + if (type(pattern) == 'string') then + if string.match(pattern, "[%^%$%(%)%%%.%[%]%*%+%?]") then + if string.match(value, pattern) then + return true + end + else + if value == pattern then + return true + end + end + end + + -- if the pattern is a table, recursively check if any of its sub-patterns match + if type(pattern) == 'table' then + for _, subPattern in pairs(pattern) do + if utils.matchesPattern(subPattern, value, msg) then + return true + end + end + end + + return false +end + +function utils.matchesSpec(msg, spec) + if type(spec) == 'function' then + return spec(msg) + -- If the spec is a table, step through every key/value pair in the pattern and check if the msg matches + -- Supported pattern types: + -- - Exact string match + -- - Lua gmatch string + -- - '_' (wildcard: Message has tag, but can be any value) + -- - Function execution on the tag, optionally using the msg as the second argument + -- - Table of patterns, where ANY of the sub-patterns matching the tag will result in a match + end + if type(spec) == 'table' then + for key, pattern in pairs(spec) do + if not msg[key] then + return false + end + if not utils.matchesPattern(pattern, msg[key], msg) then + return false + end + end + return true + end + if type(spec) == 'string' and msg.Action and msg.Action == spec then + return true + end + return false +end + +local function isArray(table) + if type(table) == "table" then + local maxIndex = 0 + for k, v in pairs(table) do + if type(k) ~= "number" or k < 1 or math.floor(k) ~= k then + return false -- If there's a non-integer key, it's not an array + end + maxIndex = math.max(maxIndex, k) + end + -- If the highest numeric index is equal to the number of elements, it's an array + return maxIndex == #table + end + return false +end + +-- @param {function} fn +-- @param {number} arity +utils.curry = function (fn, arity) + assert(type(fn) == "function", "function is required as first argument") + arity = arity or debug.getinfo(fn, "u").nparams + if arity < 2 then return fn end + + return function (...) + local args = {...} + + if #args >= arity then + return fn(table.unpack(args)) + else + return utils.curry(function (...) + return fn(table.unpack(args), ...) + end, arity - #args) + end + end +end + +--- Concat two Array Tables. +-- @param {table} a +-- @param {table} b +utils.concat = utils.curry(function (a, b) + assert(type(a) == "table", "first argument should be a table that is an array") + assert(type(b) == "table", "second argument should be a table that is an array") + assert(isArray(a), "first argument should be a table") + assert(isArray(b), "second argument should be a table") + + local result = {} + for i = 1, #a do + result[#result + 1] = a[i] + end + for i = 1, #b do + result[#result + 1] = b[i] + end + return result + --return table.concat(a,b) +end, 2) + +--- reduce applies a function to a table +-- @param {function} fn +-- @param {any} initial +-- @param {table} t +utils.reduce = utils.curry(function (fn, initial, t) + assert(type(fn) == "function", "first argument should be a function that accepts (result, value, key)") + assert(type(t) == "table" and isArray(t), "third argument should be a table that is an array") + local result = initial + for k, v in pairs(t) do + if result == nil then + result = v + else + result = fn(result, v, k) + end + end + return result +end, 3) + +-- @param {function} fn +-- @param {table} data +utils.map = utils.curry(function (fn, data) + assert(type(fn) == "function", "first argument should be a unary function") + assert(type(data) == "table" and isArray(data), "second argument should be an Array") + + local function map (result, v, k) + result[k] = fn(v, k) + return result + end + + return utils.reduce(map, {}, data) +end, 2) + +-- @param {function} fn +-- @param {table} data +utils.filter = utils.curry(function (fn, data) + assert(type(fn) == "function", "first argument should be a unary function") + assert(type(data) == "table" and isArray(data), "second argument should be an Array") + + local function filter (result, v, _k) + if fn(v) then + table.insert(result, v) + end + return result + end + + return utils.reduce(filter,{}, data) +end, 2) + +-- @param {function} fn +-- @param {table} t +utils.find = utils.curry(function (fn, t) + assert(type(fn) == "function", "first argument should be a unary function") + assert(type(t) == "table", "second argument should be a table that is an array") + for _, v in pairs(t) do + if fn(v) then + return v + end + end +end, 2) + +-- @param {string} propName +-- @param {string} value +-- @param {table} object +utils.propEq = utils.curry(function (propName, value, object) + assert(type(propName) == "string", "first argument should be a string") + -- assert(type(value) == "string", "second argument should be a string") + assert(type(object) == "table", "third argument should be a table") + + return object[propName] == value +end, 3) + +-- @param {table} data +utils.reverse = function (data) + assert(type(data) == "table", "argument needs to be a table that is an array") + return utils.reduce( + function (result, v, i) + result[#data - i + 1] = v + return result + end, + {}, + data + ) +end + +-- @param {function} ... +utils.compose = utils.curry(function (...) + local mutations = utils.reverse({...}) + + return function (v) + local result = v + for _, fn in pairs(mutations) do + assert(type(fn) == "function", "each argument needs to be a function") + result = fn(result) + end + return result + end +end, 2) + +-- @param {string} propName +-- @param {table} object +utils.prop = utils.curry(function (propName, object) + return object[propName] +end, 2) + +-- @param {any} val +-- @param {table} t +utils.includes = utils.curry(function (val, t) + assert(type(t) == "table", "argument needs to be a table") + return utils.find(function (v) return v == val end, t) ~= nil +end, 2) + +-- @param {table} t +utils.keys = function (t) + assert(type(t) == "table", "argument needs to be a table") + local keys = {} + for key in pairs(t) do + table.insert(keys, key) + end + return keys +end + +-- @param {table} t +utils.values = function (t) + assert(type(t) == "table", "argument needs to be a table") + local values = {} + for _, value in pairs(t) do + table.insert(values, value) + end + return values +end + +return utils diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.md b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.md new file mode 100644 index 0000000..565c427 --- /dev/null +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.md @@ -0,0 +1,89 @@ +# Lua Utils Module Documentation + +## Module Overview + +The Lua Utils module provides a collection of utility functions for functional programming in Lua. It includes functions for array manipulation such as concatenation, mapping, reduction, filtering, and finding elements, as well as a property equality checker. + +## Module Functions + +### 1. `concat` + +Concatenates two arrays. + +- **Syntax:** `utils.concat(a)(b)` +- **Parameters:** + - `a` (table): The first array. + - `b` (table): The second array. +- **Returns:** A new array containing all elements from `a` followed by all elements from `b`. +- **Example:** `utils.concat({1, 2})({3, 4}) -- returns {1, 2, 3, 4}` + +### 2. `map` + +Applies a function to each element of an array. + +- **Syntax:** `utils.map(fn)(t)` +- **Parameters:** + - `fn` (function): A function to apply to each element. + - `t` (table): The array to map over. +- **Returns:** A new array with each element being the result of applying `fn`. +- **Example:** `utils.map(function(x) return x * 2 end)({1, 2, 3}) -- returns {2, 4, 6}` + +### 3. `reduce` + +Reduces an array to a single value by iteratively applying a function. + +- **Syntax:** `utils.reduce(fn)(initial)(t)` +- **Parameters:** + - `fn` (function): A function to apply. + - `initial`: Initial accumulator value. + - `t` (table): The array to reduce. +- **Returns:** The final accumulated value. +- **Example:** `utils.reduce(function(acc, x) return acc + x end)(0)({1, 2, 3}) -- returns 6` + +### 4. `filter` + +Filters an array based on a predicate function. + +- **Syntax:** `utils.filter(fn)(t)` +- **Parameters:** + - `fn` (function): A predicate function to determine if an element should be included. + - `t` (table): The array to filter. +- **Returns:** A new array containing only elements that satisfy `fn`. +- **Example:** `utils.filter(function(x) return x > 1 end)({1, 2, 3}) -- returns {2, 3}` + +### 5. `find` + +Finds the first element in an array that satisfies a predicate function. + +- **Syntax:** `utils.find(fn)(t)` +- **Parameters:** + - `fn` (function): A predicate function. + - `t` (table): The array to search. +- **Returns:** The first element that satisfies `fn`, or `nil` if none do. +- **Example:** `utils.find(function(x) return x > 1 end)({1, 2, 3}) -- returns 2` + +### 6. `propEq` + +Checks if a specified property of an object equals a given value. + +- **Syntax:** `utils.propEq(propName)(value)(object)` +- **Parameters:** + - `propName` (string): The name of the property. + - `value` (string): The value to compare against. + - `object` (table): The object to check. +- **Returns:** `true` if `object[propName]` equals `value`, otherwise `false`. +- **Example:** `utils.propEq("name")("Lua")({name = "Lua"}) -- returns true` + +## Version + +- The module is currently at version 0.0.1. + +## Notes + +- This module is designed for functional programming style in Lua. +- It's important to ensure that inputs to these functions are of correct types as expected by each function. +- The module does not modify the original arrays but returns new arrays or values. + +--- + +This documentation provides a basic overview and examples for each function in the Utils module. Users should adapt the examples to their specific use cases. diff --git a/yarn-error.log b/yarn-error.log new file mode 100644 index 0000000..1d1dcba --- /dev/null +++ b/yarn-error.log @@ -0,0 +1,309 @@ +Arguments: + /home/stephen/.nvm/versions/node/v20.18.0/bin/node /home/stephen/.yarn/bin/yarn.js build:vault + +PATH: + /usr/local/go/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/root/.ao/bin:/home/stephen/.yarn/bin:/home/stephen/.config/yarn/global/node_modules/.bin:/usr/local/go/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/root/.ao/bin:/home/stephen/.yarn/bin:/home/stephen/.config/yarn/global/node_modules/.bin:/home/stephen/.nvm/versions/node/v20.18.0/bin:/home/stephen/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin:/usr/local/go/bin + +Yarn version: + 1.22.19 + +Node version: + 20.18.0 + +Platform: + linux x64 + +Trace: + SyntaxError: /home/stephen/Documents/code/permahacks24/secretorium/package.json: Expected double-quoted property name in JSON at position 513 + at JSON.parse () + at /home/stephen/.yarn/lib/cli.js:1629:59 + at Generator.next () + at step (/home/stephen/.yarn/lib/cli.js:310:30) + at /home/stephen/.yarn/lib/cli.js:321:13 + +npm manifest: + { + "name": "secretorium", + "version": "1.0.0", + "main": "index.js", + "repository": "https://github.com/project-kardeshev/secretorium", + "author": "bobinstein ", + "license": "MIT", + "type": "module", + "scripts": { + "build:registry": "node tools/bundle-aos.js --path=\"./processes/registry.lua\" --output=\"./dist/registry/aos-bundled.lua\"", + "build:vault": "node tools/bundle-aos.js --path=\"./processes/vault.lua\" --output=\"./dist/vault/aos-bundled.lua\"" + }, + }, + "devDependencies": { + "@permaweb/ao-loader": "^0.0.43", + "@permaweb/aoconnect": "^0.0.59", + "arweave": "^1.15.5", + "fs-extra": "^11.2.0", + "prettier": "^3.3.3" + } + } + +yarn manifest: + No manifest + +Lockfile: + # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. + # yarn lockfile v1 + + + "@fastify/busboy@^2.0.0": + version "2.1.1" + resolved "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" + integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== + + "@permaweb/ao-loader@^0.0.43": + version "0.0.43" + resolved "https://registry.npmjs.org/@permaweb/ao-loader/-/ao-loader-0.0.43.tgz#bf980be06ec397ad475a023550b5526bf53e2a9b" + integrity sha512-xPYzyKSCqtL0U8oUcCrW+uPpm7IcMncM5IPVGCGKljxA3IQA/HI8S5XA6tcZUaDRCl8VSVsJzqOgkdzy1JGi5w== + dependencies: + "@permaweb/wasm-metering" "^0.2.2" + + "@permaweb/ao-scheduler-utils@~0.0.23": + version "0.0.24" + resolved "https://registry.npmjs.org/@permaweb/ao-scheduler-utils/-/ao-scheduler-utils-0.0.24.tgz#a7d2c1e09f9b6ea5d45127fa395bbbcef6688452" + integrity sha512-G6109Nz8+dQFPuG7mV8mz66kLVA+gl2uTSqU7qpaRwfujrWi6obM94CpmvyvAnrLo3dB29EYiuv7+KOKcns8ig== + dependencies: + lru-cache "^10.2.2" + ramda "^0.30.0" + zod "^3.23.5" + + "@permaweb/aoconnect@^0.0.59": + version "0.0.59" + resolved "https://registry.npmjs.org/@permaweb/aoconnect/-/aoconnect-0.0.59.tgz#0ceb6257e2f376f4af6783163e3037240560dff1" + integrity sha512-AgnUv50hp3BVvqWk2IOw3p9ksF2GblFwQHCIWqwTYjvdViCPlsL4gx/BefxFqbMABXQ5q2QJItMJLlPelqC2CQ== + dependencies: + "@permaweb/ao-scheduler-utils" "~0.0.23" + buffer "^6.0.3" + debug "^4.3.6" + hyper-async "^1.1.2" + mnemonist "^0.39.8" + ramda "^0.30.1" + warp-arbundles "^1.0.4" + zod "^3.23.8" + + "@permaweb/wasm-json-toolkit@^0.2.9": + version "0.2.9" + resolved "https://registry.npmjs.org/@permaweb/wasm-json-toolkit/-/wasm-json-toolkit-0.2.9.tgz#241cdf37c1690a751a73dd05987336142e5576d1" + integrity sha512-CGCeUwS+UeqUdvORiyG0LykkQXLTwS5TWc590CUkDfOYyBUSPv8pse0sJStvTC9LKAzuNx3ELBvmqHCI4muUAA== + dependencies: + buffer-pipe "0.0.3" + leb128 "0.0.4" + safe-buffer "^5.1.2" + + "@permaweb/wasm-metering@^0.2.2": + version "0.2.2" + resolved "https://registry.npmjs.org/@permaweb/wasm-metering/-/wasm-metering-0.2.2.tgz#a854c485d9ddbbefb4a17a3692822b696d54a2c7" + integrity sha512-xM2MbPkHc4rzhTR6VH5eXtfC+liaYSuNCa0kPRaqSZO2gr1SirJWnzUBDa5VOfTBTgIlIVv5RW+Mkbt/VuK+oA== + dependencies: + "@permaweb/wasm-json-toolkit" "^0.2.9" + leb128 "^0.0.4" + + arconnect@^0.4.2: + version "0.4.2" + resolved "https://registry.npmjs.org/arconnect/-/arconnect-0.4.2.tgz#83de7638fb46183e82d7ec7efb5594c5f7cdc806" + integrity sha512-Jkpd4QL3TVqnd3U683gzXmZUVqBUy17DdJDuL/3D9rkysLgX6ymJ2e+sR+xyZF5Rh42CBqDXWNMmCjBXeP7Gbw== + dependencies: + arweave "^1.10.13" + + arweave@^1.10.13, arweave@^1.13.7, arweave@^1.15.5: + version "1.15.5" + resolved "https://registry.npmjs.org/arweave/-/arweave-1.15.5.tgz#d0fb209de01bfc9dc97d5da70270928a83ecee83" + integrity sha512-Zj3b8juz1ZtDaQDPQlzWyk2I4wZPx3RmcGq8pVJeZXl2Tjw0WRy5ueHPelxZtBLqCirGoZxZEAFRs6SZUSCBjg== + dependencies: + arconnect "^0.4.2" + asn1.js "^5.4.1" + base64-js "^1.5.1" + bignumber.js "^9.0.2" + + asn1.js@^5.4.1: + version "5.4.1" + resolved "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + + base64-js@^1.3.1, base64-js@^1.5.1: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + + base64url@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" + integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + + bignumber.js@^9.0.2: + version "9.1.2" + resolved "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + + bn.js@^4.0.0, bn.js@^4.11.6: + version "4.12.0" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + + buffer-pipe@0.0.0: + version "0.0.0" + resolved "https://registry.npmjs.org/buffer-pipe/-/buffer-pipe-0.0.0.tgz#186ec257d696e8e74c3051160a0e9e9a9811a387" + integrity sha512-PvKbsvQOH4dcUyUEvQQSs3CIkkuPcOHt3gKnXwf4HsPKFDxSN7bkmICVIWgOmW/jx/fAEGGn4mIayIJPLs7G8g== + dependencies: + safe-buffer "^5.1.1" + + buffer-pipe@0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/buffer-pipe/-/buffer-pipe-0.0.3.tgz#242197681d4591e7feda213336af6c07a5ce2409" + integrity sha512-GlxfuD/NrKvCNs0Ut+7b1IHjylfdegMBxQIlZHj7bObKVQBxB5S84gtm2yu1mQ8/sSggceWBDPY0cPXgvX2MuA== + dependencies: + safe-buffer "^5.1.2" + + buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + + debug@^4.3.6: + version "4.3.7" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + + fs-extra@^11.2.0: + version "11.2.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" + integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + + graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.11" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + + hyper-async@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/hyper-async/-/hyper-async-1.1.2.tgz#b9a83be36e726bface6f4a5b84f1a1a25bf19e6a" + integrity sha512-cnpOgKa+5FZOaccTtjduac1FrZuSc38/ftCp3vYJdUMt+7c+uvGDKLDK4MTNK8D3aFjIeveVrPcSgUPvzZLopg== + + ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + + inherits@^2.0.1: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + + jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + + leb128@0.0.4, leb128@^0.0.4: + version "0.0.4" + resolved "https://registry.npmjs.org/leb128/-/leb128-0.0.4.tgz#f96d698cf3ba5b677423abfe50b7e9b2df1463ff" + integrity sha512-2zejk0fCIgY8RVcc/KzvyfpDio5Oo8HgPZmkrOmdwmbk0KpKpgD+JKwikxKk8cZYkANIhwHK50SNukkCm3XkCQ== + dependencies: + bn.js "^4.11.6" + buffer-pipe "0.0.0" + + lru-cache@^10.2.2: + version "10.4.3" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + + minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + + mnemonist@^0.39.8: + version "0.39.8" + resolved "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.8.tgz#9078cd8386081afd986cca34b52b5d84ea7a4d38" + integrity sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ== + dependencies: + obliterator "^2.0.1" + + ms@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + + obliterator@^2.0.1: + version "2.0.4" + resolved "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz#fa650e019b2d075d745e44f1effeb13a2adbe816" + integrity sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ== + + prettier@^3.3.3: + version "3.3.3" + resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + + ramda@^0.30.0, ramda@^0.30.1: + version "0.30.1" + resolved "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz#7108ac95673062b060025052cd5143ae8fc605bf" + integrity sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw== + + safe-buffer@^5.1.1, safe-buffer@^5.1.2: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + + safer-buffer@^2.1.0: + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + + undici@^5.19.1: + version "5.28.4" + resolved "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" + integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g== + dependencies: + "@fastify/busboy" "^2.0.0" + + universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + + warp-arbundles@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/warp-arbundles/-/warp-arbundles-1.0.4.tgz#10c0cd662ab41b0dabad9159c7110f43425cc5cc" + integrity sha512-KeRac/EJ7VOK+v5+PSMh2SrzpCKOAFnJICLlqZWt6qPkDCzVwcrNE5wFxOlEk5U170ewMDAB3e86UHUblevXpw== + dependencies: + arweave "^1.13.7" + base64url "^3.0.1" + buffer "^6.0.3" + warp-isomorphic "^1.0.7" + + warp-isomorphic@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/warp-isomorphic/-/warp-isomorphic-1.0.7.tgz#abf1ee7bce44bec7c6b97547859e614876869aa7" + integrity sha512-fXHbUXwdYqPm9fRPz8mjv5ndPco09aMQuTe4kXfymzOq8V6F3DLsg9cIafxvjms9/mc6eijzkLBJ63yjEENEjA== + dependencies: + buffer "^6.0.3" + undici "^5.19.1" + + zod@^3.23.5, zod@^3.23.8: + version "3.23.8" + resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..c42972f --- /dev/null +++ b/yarn.lock @@ -0,0 +1,259 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@fastify/busboy@^2.0.0": + version "2.1.1" + resolved "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" + integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== + +"@permaweb/ao-loader@^0.0.43": + version "0.0.43" + resolved "https://registry.npmjs.org/@permaweb/ao-loader/-/ao-loader-0.0.43.tgz#bf980be06ec397ad475a023550b5526bf53e2a9b" + integrity sha512-xPYzyKSCqtL0U8oUcCrW+uPpm7IcMncM5IPVGCGKljxA3IQA/HI8S5XA6tcZUaDRCl8VSVsJzqOgkdzy1JGi5w== + dependencies: + "@permaweb/wasm-metering" "^0.2.2" + +"@permaweb/ao-scheduler-utils@~0.0.23": + version "0.0.24" + resolved "https://registry.npmjs.org/@permaweb/ao-scheduler-utils/-/ao-scheduler-utils-0.0.24.tgz#a7d2c1e09f9b6ea5d45127fa395bbbcef6688452" + integrity sha512-G6109Nz8+dQFPuG7mV8mz66kLVA+gl2uTSqU7qpaRwfujrWi6obM94CpmvyvAnrLo3dB29EYiuv7+KOKcns8ig== + dependencies: + lru-cache "^10.2.2" + ramda "^0.30.0" + zod "^3.23.5" + +"@permaweb/aoconnect@^0.0.59": + version "0.0.59" + resolved "https://registry.npmjs.org/@permaweb/aoconnect/-/aoconnect-0.0.59.tgz#0ceb6257e2f376f4af6783163e3037240560dff1" + integrity sha512-AgnUv50hp3BVvqWk2IOw3p9ksF2GblFwQHCIWqwTYjvdViCPlsL4gx/BefxFqbMABXQ5q2QJItMJLlPelqC2CQ== + dependencies: + "@permaweb/ao-scheduler-utils" "~0.0.23" + buffer "^6.0.3" + debug "^4.3.6" + hyper-async "^1.1.2" + mnemonist "^0.39.8" + ramda "^0.30.1" + warp-arbundles "^1.0.4" + zod "^3.23.8" + +"@permaweb/wasm-json-toolkit@^0.2.9": + version "0.2.9" + resolved "https://registry.npmjs.org/@permaweb/wasm-json-toolkit/-/wasm-json-toolkit-0.2.9.tgz#241cdf37c1690a751a73dd05987336142e5576d1" + integrity sha512-CGCeUwS+UeqUdvORiyG0LykkQXLTwS5TWc590CUkDfOYyBUSPv8pse0sJStvTC9LKAzuNx3ELBvmqHCI4muUAA== + dependencies: + buffer-pipe "0.0.3" + leb128 "0.0.4" + safe-buffer "^5.1.2" + +"@permaweb/wasm-metering@^0.2.2": + version "0.2.2" + resolved "https://registry.npmjs.org/@permaweb/wasm-metering/-/wasm-metering-0.2.2.tgz#a854c485d9ddbbefb4a17a3692822b696d54a2c7" + integrity sha512-xM2MbPkHc4rzhTR6VH5eXtfC+liaYSuNCa0kPRaqSZO2gr1SirJWnzUBDa5VOfTBTgIlIVv5RW+Mkbt/VuK+oA== + dependencies: + "@permaweb/wasm-json-toolkit" "^0.2.9" + leb128 "^0.0.4" + +arconnect@^0.4.2: + version "0.4.2" + resolved "https://registry.npmjs.org/arconnect/-/arconnect-0.4.2.tgz#83de7638fb46183e82d7ec7efb5594c5f7cdc806" + integrity sha512-Jkpd4QL3TVqnd3U683gzXmZUVqBUy17DdJDuL/3D9rkysLgX6ymJ2e+sR+xyZF5Rh42CBqDXWNMmCjBXeP7Gbw== + dependencies: + arweave "^1.10.13" + +arweave@^1.10.13, arweave@^1.13.7, arweave@^1.15.5: + version "1.15.5" + resolved "https://registry.npmjs.org/arweave/-/arweave-1.15.5.tgz#d0fb209de01bfc9dc97d5da70270928a83ecee83" + integrity sha512-Zj3b8juz1ZtDaQDPQlzWyk2I4wZPx3RmcGq8pVJeZXl2Tjw0WRy5ueHPelxZtBLqCirGoZxZEAFRs6SZUSCBjg== + dependencies: + arconnect "^0.4.2" + asn1.js "^5.4.1" + base64-js "^1.5.1" + bignumber.js "^9.0.2" + +asn1.js@^5.4.1: + version "5.4.1" + resolved "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + +base64-js@^1.3.1, base64-js@^1.5.1: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +base64url@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" + integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + +bignumber.js@^9.0.2: + version "9.1.2" + resolved "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + +bn.js@^4.0.0, bn.js@^4.11.6: + version "4.12.0" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +buffer-pipe@0.0.0: + version "0.0.0" + resolved "https://registry.npmjs.org/buffer-pipe/-/buffer-pipe-0.0.0.tgz#186ec257d696e8e74c3051160a0e9e9a9811a387" + integrity sha512-PvKbsvQOH4dcUyUEvQQSs3CIkkuPcOHt3gKnXwf4HsPKFDxSN7bkmICVIWgOmW/jx/fAEGGn4mIayIJPLs7G8g== + dependencies: + safe-buffer "^5.1.1" + +buffer-pipe@0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/buffer-pipe/-/buffer-pipe-0.0.3.tgz#242197681d4591e7feda213336af6c07a5ce2409" + integrity sha512-GlxfuD/NrKvCNs0Ut+7b1IHjylfdegMBxQIlZHj7bObKVQBxB5S84gtm2yu1mQ8/sSggceWBDPY0cPXgvX2MuA== + dependencies: + safe-buffer "^5.1.2" + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +debug@^4.3.6: + version "4.3.7" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + +fs-extra@^11.2.0: + version "11.2.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" + integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.11" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +hyper-async@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/hyper-async/-/hyper-async-1.1.2.tgz#b9a83be36e726bface6f4a5b84f1a1a25bf19e6a" + integrity sha512-cnpOgKa+5FZOaccTtjduac1FrZuSc38/ftCp3vYJdUMt+7c+uvGDKLDK4MTNK8D3aFjIeveVrPcSgUPvzZLopg== + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +inherits@^2.0.1: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +leb128@0.0.4, leb128@^0.0.4: + version "0.0.4" + resolved "https://registry.npmjs.org/leb128/-/leb128-0.0.4.tgz#f96d698cf3ba5b677423abfe50b7e9b2df1463ff" + integrity sha512-2zejk0fCIgY8RVcc/KzvyfpDio5Oo8HgPZmkrOmdwmbk0KpKpgD+JKwikxKk8cZYkANIhwHK50SNukkCm3XkCQ== + dependencies: + bn.js "^4.11.6" + buffer-pipe "0.0.0" + +lru-cache@^10.2.2: + version "10.4.3" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +mnemonist@^0.39.8: + version "0.39.8" + resolved "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.8.tgz#9078cd8386081afd986cca34b52b5d84ea7a4d38" + integrity sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ== + dependencies: + obliterator "^2.0.1" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +obliterator@^2.0.1: + version "2.0.4" + resolved "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz#fa650e019b2d075d745e44f1effeb13a2adbe816" + integrity sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ== + +prettier@^3.3.3: + version "3.3.3" + resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + +ramda@^0.30.0, ramda@^0.30.1: + version "0.30.1" + resolved "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz#7108ac95673062b060025052cd5143ae8fc605bf" + integrity sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw== + +safe-buffer@^5.1.1, safe-buffer@^5.1.2: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safer-buffer@^2.1.0: + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +undici@^5.19.1: + version "5.28.4" + resolved "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" + integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g== + dependencies: + "@fastify/busboy" "^2.0.0" + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +warp-arbundles@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/warp-arbundles/-/warp-arbundles-1.0.4.tgz#10c0cd662ab41b0dabad9159c7110f43425cc5cc" + integrity sha512-KeRac/EJ7VOK+v5+PSMh2SrzpCKOAFnJICLlqZWt6qPkDCzVwcrNE5wFxOlEk5U170ewMDAB3e86UHUblevXpw== + dependencies: + arweave "^1.13.7" + base64url "^3.0.1" + buffer "^6.0.3" + warp-isomorphic "^1.0.7" + +warp-isomorphic@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/warp-isomorphic/-/warp-isomorphic-1.0.7.tgz#abf1ee7bce44bec7c6b97547859e614876869aa7" + integrity sha512-fXHbUXwdYqPm9fRPz8mjv5ndPco09aMQuTe4kXfymzOq8V6F3DLsg9cIafxvjms9/mc6eijzkLBJ63yjEENEjA== + dependencies: + buffer "^6.0.3" + undici "^5.19.1" + +zod@^3.23.5, zod@^3.23.8: + version "3.23.8" + resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== From 2a6d8371a686735591820c5f98d160d9c8b5c18f Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Thu, 31 Oct 2024 18:49:31 -0600 Subject: [PATCH 5/9] fix(formatting): format --- .../processes/secretorium_registry.ts | 4 +- tools/build-aos-flavor.js | 106 ++--- tools/bundle-aos.js | 25 +- tools/constants.js | 50 +-- tools/lua-bundler.js | 22 +- tools/publish-aos.js | 28 +- tools/scripts/install-deps.js | 14 +- tools/spawn-aos.js | 30 +- tools/utils.js | 15 +- .../aos-process/repl.js | 36 +- .../aos-process/test/assignment.test.js | 393 +++++++++--------- .../test/crypto/cipher/aes.test.js | 56 +-- .../test/crypto/cipher/issac.test.js | 40 +- .../test/crypto/cipher/morus.test.js | 44 +- .../test/crypto/cipher/norx.test.js | 42 +- .../test/crypto/digest/blake2b.test.js | 44 +- .../test/crypto/digest/md2.test.js | 44 +- .../test/crypto/digest/md4.test.js | 44 +- .../test/crypto/digest/md5.test.js | 44 +- .../test/crypto/digest/sha1.test.js | 44 +- .../test/crypto/digest/sha2.test.js | 40 +- .../test/crypto/digest/sha3.test.js | 44 +- .../test/crypto/kdf/pbkdf2.test.js | 36 +- .../aos-process/test/crypto/mac/hmac.test.js | 42 +- .../aos-process/test/crypto/random.test.js | 34 +- .../aos-process/test/eval.test.js | 112 ++--- .../aos-process/test/handlers.test.js | 242 +++++------ .../aos-process/test/inbox.test.js | 52 +-- .../aos-process/test/magic-table.test.js | 62 +-- .../aos-process/test/print.test.js | 114 ++--- .../aos-process/test/random.test.js | 38 +- .../aos-process/test/state.test.js | 66 +-- 32 files changed, 1008 insertions(+), 999 deletions(-) diff --git a/src/services/processes/secretorium_registry.ts b/src/services/processes/secretorium_registry.ts index 9220069..f00088c 100644 --- a/src/services/processes/secretorium_registry.ts +++ b/src/services/processes/secretorium_registry.ts @@ -1,4 +1,4 @@ -import { Process, ProcessReadable, AO } from '@project-kardeshev/ao-sdk'; +import { AO, Process, ProcessReadable } from '@project-kardeshev/ao-sdk'; import { SECRETORIUM_REGISTRY_ID } from '@src/constants'; interface SecretoriumRegistryReadable { @@ -15,7 +15,7 @@ export class SecretoriumRegistryProcessReadable { constructor({ processId = SECRETORIUM_REGISTRY_ID }: { processId: string }) { this.process = Process.init({ processId, - ao: new AO({}) + ao: new AO({}), }) as ProcessReadable; } } diff --git a/tools/build-aos-flavor.js b/tools/build-aos-flavor.js index dafc1a8..cd9a807 100644 --- a/tools/build-aos-flavor.js +++ b/tools/build-aos-flavor.js @@ -1,12 +1,14 @@ -import { readFileSync, existsSync, mkdirSync, writeFileSync } from "fs"; -import * as fs from "fs-extra"; // Import fs-extra for recursive directory copying -import * as path from "path"; -import { bundle } from "./lua-bundler.js"; -import { fileURLToPath } from "url"; -import { randomUUID } from "crypto"; -import Docker from "dockerode"; -import { exec } from "child_process"; -import util from "util"; +import { exec } from 'child_process'; +import { randomUUID } from 'crypto'; +import Docker from 'dockerode'; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; +import * as fs from 'fs-extra'; +// Import fs-extra for recursive directory copying +import * as path from 'path'; +import { fileURLToPath } from 'url'; +import util from 'util'; + +import { bundle } from './lua-bundler.js'; const execPromise = util.promisify(exec); // Promisify exec for async/await usage const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -14,41 +16,41 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); const docker = new Docker(); // Initialize dockerode instance const args = process.argv.slice(2); // Get CLI arguments -const pathArg = args.find((arg) => arg.startsWith("--path=")); -const outputArg = args.find((arg) => arg.startsWith("--output=")); +const pathArg = args.find((arg) => arg.startsWith('--path=')); +const outputArg = args.find((arg) => arg.startsWith('--output=')); if (!pathArg || !outputArg) { - console.error("Please provide both a --path and --output as CLI arguments."); + console.error('Please provide both a --path and --output as CLI arguments.'); process.exit(1); } -const pathToLua = pathArg.split("=")[1]; -const outputPath = outputArg.split("=")[1]; +const pathToLua = pathArg.split('=')[1]; +const outputPath = outputArg.split('=')[1]; // Create working directory and copy AOS files to it. Set random id to avoid conflicts const id = randomUUID(); -const workingDir = path.join(__dirname, "working-" + id); +const workingDir = path.join(__dirname, 'working-' + id); if (!existsSync(workingDir)) { mkdirSync(workingDir, { recursive: true }); } // Copy the entire aos/process directory to the working directory -const sourceDir = path.join(__dirname, "..", "aos", "process"); +const sourceDir = path.join(__dirname, '..', 'aos', 'process'); if (!existsSync(sourceDir)) { - await execPromise("git submodule update --init --recursive"); + await execPromise('git submodule update --init --recursive'); } if (!existsSync(sourceDir)) { console.error( - "AOS process directory not found. Please ensure the submodule has been initialized.", + 'AOS process directory not found. Please ensure the submodule has been initialized.', ); process.exit(1); } -const destDir = path.join(workingDir, "aos-process"); +const destDir = path.join(workingDir, 'aos-process'); try { await fs.copy(sourceDir, destDir); - console.log("Successfully copied AOS process directory"); + console.log('Successfully copied AOS process directory'); } catch (err) { - console.error("Error copying AOS process directory:", err); + console.error('Error copying AOS process directory:', err); process.exit(1); } @@ -56,25 +58,25 @@ async function checkAndStartDocker() { try { // Check if Docker is running await docker.ping(); - console.log("Docker is running."); + console.log('Docker is running.'); } catch (err) { - console.error("Docker is not running. Attempting to start Docker..."); + console.error('Docker is not running. Attempting to start Docker...'); - if (process.platform === "darwin") { + if (process.platform === 'darwin') { try { // macOS: Start Docker Desktop - console.log("Attempting to start Docker Desktop on macOS..."); - await execPromise("open -a Docker"); + console.log('Attempting to start Docker Desktop on macOS...'); + await execPromise('open -a Docker'); // Wait for Docker to start await new Promise((resolve) => setTimeout(resolve, 5000)); // Recheck if Docker is running await docker.ping(); - console.log("Docker has been started and is running."); + console.log('Docker has been started and is running.'); } catch (startErr) { console.error( - "Failed to start Docker on macOS. Please ensure Docker Desktop is installed and running.", + 'Failed to start Docker on macOS. Please ensure Docker Desktop is installed and running.', startErr.message, ); process.exit(1); @@ -82,12 +84,12 @@ async function checkAndStartDocker() { } else { try { // Linux: Use systemctl to start Docker - console.log("Attempting to start Docker on Linux..."); - await execPromise("sudo systemctl start docker"); - console.log("Docker has been started."); + console.log('Attempting to start Docker on Linux...'); + await execPromise('sudo systemctl start docker'); + console.log('Docker has been started.'); } catch (startErr) { console.error( - "Failed to start Docker on Linux. Please ensure Docker is installed and you have permissions to start it.", + 'Failed to start Docker on Linux. Please ensure Docker is installed and you have permissions to start it.', startErr.message, ); process.exit(1); @@ -99,19 +101,19 @@ async function checkAndStartDocker() { async function checkAoCLI() { try { // Check if `ao` CLI is installed by running a simple version check - await execPromise("ao --version"); - console.log("ao CLI is available."); + await execPromise('ao --version'); + console.log('ao CLI is available.'); } catch (err) { // If the ao CLI is not installed, install it with yarn install-deps console.log( - "ao CLI is not available. Installing dependencies with yarn install-deps...", + 'ao CLI is not available. Installing dependencies with yarn install-deps...', ); try { - await execPromise("yarn install-deps"); - console.log("ao CLI has been installed successfully."); + await execPromise('yarn install-deps'); + console.log('ao CLI has been installed successfully.'); } catch (installErr) { console.error( - "Failed to install ao CLI dependencies.", + 'Failed to install ao CLI dependencies.', installErr.message, ); process.exit(1); @@ -129,42 +131,42 @@ async function main() { const bundledLua = bundle(pathToLua); // Write bundled Lua to /aos-process/process.lua above "return process" - const processLuaPath = path.join(workingDir, "aos-process", "process.lua"); - const processLua = readFileSync(processLuaPath, "utf8"); - const processLuaParts = processLua.split("return process"); + const processLuaPath = path.join(workingDir, 'aos-process', 'process.lua'); + const processLua = readFileSync(processLuaPath, 'utf8'); + const processLuaParts = processLua.split('return process'); const newProcessLua = processLuaParts[0] + - "\n" + + '\n' + bundledLua + - "\nreturn process" + + '\nreturn process' + processLuaParts[1]; writeFileSync(processLuaPath, newProcessLua); // Build AOS wasm binary const buildCmd = `cd ${workingDir}/aos-process && ao build`; - console.log("Building AOS binary with command:", buildCmd); + console.log('Building AOS binary with command:', buildCmd); try { const { stdout, stderr } = await execPromise(buildCmd); if (stderr) { - console.error("Build error:", stderr); + console.error('Build error:', stderr); } - console.log("Build result:", stdout); + console.log('Build result:', stdout); } catch (buildErr) { - console.error("Failed to build AOS binary:", buildErr.message); + console.error('Failed to build AOS binary:', buildErr.message); process.exit(1); } // Move built wasm (process.wasm) to output path - const wasmPath = path.join(workingDir, "aos-process", "process.wasm"); - const wasmOutputPath = path.join(outputPath, "process.wasm"); + const wasmPath = path.join(workingDir, 'aos-process', 'process.wasm'); + const wasmOutputPath = path.join(outputPath, 'process.wasm'); await fs.copy(wasmPath, wasmOutputPath); - console.log("WASM has been built and saved to", wasmOutputPath); + console.log('WASM has been built and saved to', wasmOutputPath); // Write bundled Lua to output path - const luaOutputPath = path.join(outputPath, "aos-bundled.lua"); + const luaOutputPath = path.join(outputPath, 'aos-bundled.lua'); writeFileSync(luaOutputPath, bundledLua); - console.log("Lua has been bundled and saved to", luaOutputPath); + console.log('Lua has been bundled and saved to', luaOutputPath); } main() diff --git a/tools/bundle-aos.js b/tools/bundle-aos.js index 0b51871..3f3888d 100644 --- a/tools/bundle-aos.js +++ b/tools/bundle-aos.js @@ -1,26 +1,27 @@ -import * as fs from "fs"; -import * as path from "path"; -import { bundle } from "./lua-bundler.js"; -import { fileURLToPath } from "url"; +import * as fs from 'fs'; +import * as path from 'path'; +import { fileURLToPath } from 'url'; + +import { bundle } from './lua-bundler.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); async function main() { const args = process.argv.slice(2); // Get CLI arguments - const pathArg = args.find((arg) => arg.startsWith("--path=")); - const outputArg = args.find((arg) => arg.startsWith("--output=")); + const pathArg = args.find((arg) => arg.startsWith('--path=')); + const outputArg = args.find((arg) => arg.startsWith('--output=')); if (!pathArg || !outputArg) { console.error( - "Please provide both a --path and --output as CLI arguments.", + 'Please provide both a --path and --output as CLI arguments.', ); return; } - const pathToLua = pathArg.split("=")[1]; - const outputPath = outputArg.split("=")[1]; + const pathToLua = pathArg.split('=')[1]; + const outputPath = outputArg.split('=')[1]; - console.log("Path to Lua:", pathToLua); - console.log("Output Path:", outputPath); + console.log('Path to Lua:', pathToLua); + console.log('Output Path:', outputPath); const bundledLua = bundle(pathToLua); @@ -30,7 +31,7 @@ async function main() { } fs.writeFileSync(outputPath, bundledLua); - console.log("Lua has been bundled and saved to", outputPath); + console.log('Lua has been bundled and saved to', outputPath); } main(); diff --git a/tools/constants.js b/tools/constants.js index 7a998dd..180f180 100644 --- a/tools/constants.js +++ b/tools/constants.js @@ -1,31 +1,31 @@ -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export const kibibyte = 1024; export const mebibyte = kibibyte * 1024; export const gibibyte = mebibyte * 1024; -export const STUB_ADDRESS = "".padEnd(43, "1"); -export const ALTERNATE_STUB_ADDRESS = "".padEnd(43, "2"); +export const STUB_ADDRESS = ''.padEnd(43, '1'); +export const ALTERNATE_STUB_ADDRESS = ''.padEnd(43, '2'); /* ao READ-ONLY Env Variables */ export const AO_LOADER_HANDLER_ENV = { Process: { - Id: "".padEnd(43, "1"), + Id: ''.padEnd(43, '1'), Owner: STUB_ADDRESS, - Tags: [{ name: "Authority", value: "XXXXXX" }], + Tags: [{ name: 'Authority', value: 'XXXXXX' }], }, Module: { - Id: "".padEnd(43, "1"), - Tags: [{ name: "Authority", value: "YYYYYY" }], + Id: ''.padEnd(43, '1'), + Tags: [{ name: 'Authority', value: 'YYYYYY' }], }, }; export const AO_LOADER_OPTIONS = { - format: "wasm64-unknown-emscripten-draft_2024_02_15", - inputEncoding: "JSON-1", - outputEncoding: "JSON-1", + format: 'wasm64-unknown-emscripten-draft_2024_02_15', + inputEncoding: 'JSON-1', + outputEncoding: 'JSON-1', memoryLimit: gibibyte, computeLimit: (9e12).toString(), extensions: [], @@ -34,38 +34,38 @@ export const AO_LOADER_OPTIONS = { export const AOS_WASM = fs.readFileSync( path.join( __dirname, - "fixtures/aos-C61NgrJDuhwGUsFca1rgfE7ehAKSdgOqPj6aYYy5u4s.wasm", + 'fixtures/aos-C61NgrJDuhwGUsFca1rgfE7ehAKSdgOqPj6aYYy5u4s.wasm', ), ); export const BUNDLED_CHESS_REGISTRY_AOS_LUA = fs.readFileSync( - path.join(__dirname, "../dist/chess/registry/aos-bundled.lua"), - "utf-8", + path.join(__dirname, '../dist/chess/registry/aos-bundled.lua'), + 'utf-8', ); export const BUNDLED_CHESS_GAME_AOS_LUA = fs.readFileSync( - path.join(__dirname, "../dist/chess/game/aos-bundled.lua"), - "utf-8", + path.join(__dirname, '../dist/chess/game/aos-bundled.lua'), + 'utf-8', ); export const DEFAULT_HANDLE_OPTIONS = { - Id: "".padEnd(43, "1"), - ["Block-Height"]: "1", + Id: ''.padEnd(43, '1'), + ['Block-Height']: '1', // important to set the address so that that `Authority` check passes. Else the `isTrusted` with throw an error. Owner: STUB_ADDRESS, - Module: "ANT", - Target: "".padEnd(43, "1"), + Module: 'ANT', + Target: ''.padEnd(43, '1'), From: STUB_ADDRESS, Timestamp: Date.now(), }; export const ALTERNATE_HANDLE_OPTIONS = { - Id: "".padEnd(43, "2"), - ["Block-Height"]: "1", + Id: ''.padEnd(43, '2'), + ['Block-Height']: '1', // important to set the address so that that `Authority` check passes. Else the `isTrusted` with throw an error. Owner: ALTERNATE_STUB_ADDRESS, - Module: "ANT", - Target: "".padEnd(43, "1"), + Module: 'ANT', + Target: ''.padEnd(43, '1'), From: ALTERNATE_STUB_ADDRESS, Timestamp: Date.now(), }; diff --git a/tools/lua-bundler.js b/tools/lua-bundler.js index 224e042..38886bc 100644 --- a/tools/lua-bundler.js +++ b/tools/lua-bundler.js @@ -1,5 +1,5 @@ -import fs from "fs"; -import path from "path"; +import fs from 'fs'; +import path from 'path'; /** * @typedef Module @@ -13,7 +13,7 @@ import path from "path"; * @returns {[string, Module[]]} */ function createExecutableFromProject(project) { - const getModFnName = (name) => name.replace(/\./g, "_").replace(/^_/, ""); + const getModFnName = (name) => name.replace(/\./g, '_').replace(/^_/, ''); /** @type {Module[]} */ const contents = []; @@ -26,9 +26,13 @@ function createExecutableFromProject(project) { const existing = contents.find((m) => m.path === mod.path); const moduleContent = (!existing && - `-- module: "${mod.name}"\nlocal function _loaded_mod_${getModFnName(mod.name)}()\n${mod.content}\nend\n`) || - ""; - const requireMapper = `\n_G.package.loaded["${mod.name}"] = _loaded_mod_${getModFnName(existing?.name || mod.name)}()`; + `-- module: "${mod.name}"\nlocal function _loaded_mod_${getModFnName( + mod.name, + )}()\n${mod.content}\nend\n`) || + ''; + const requireMapper = `\n_G.package.loaded["${ + mod.name + }"] = _loaded_mod_${getModFnName(existing?.name || mod.name)}()`; contents.push({ ...mod, @@ -40,7 +44,7 @@ function createExecutableFromProject(project) { contents.push(project[project.length - 1]); return [ - contents.reduce((acc, con) => acc + "\n\n" + con.content, ""), + contents.reduce((acc, con) => acc + '\n\n' + con.content, ''), contents, ]; } @@ -92,13 +96,13 @@ function exploreNodes(node, cwd) { if (!fs.existsSync(node.path)) return []; // set content - node.content = fs.readFileSync(node.path, "utf-8"); + node.content = fs.readFileSync(node.path, 'utf-8'); const requirePattern = /(?<=(require( *)(\n*)(\()?( *)("|'))).*(?=("|'))/g; const requiredModules = node.content.match(requirePattern)?.map((mod) => ({ name: mod, - path: path.join(cwd, mod.replace(/\./g, "/") + ".lua"), + path: path.join(cwd, mod.replace(/\./g, '/') + '.lua'), content: undefined, })) || []; diff --git a/tools/publish-aos.js b/tools/publish-aos.js index c071c57..b9f6e79 100644 --- a/tools/publish-aos.js +++ b/tools/publish-aos.js @@ -1,34 +1,34 @@ -import Arweave from "arweave"; -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; +import Arweave from 'arweave'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const arweave = Arweave.init({ - host: "arweave.net", + host: 'arweave.net', port: 443, - protocol: "https", + protocol: 'https', }); async function main() { const bundledLua = fs.readFileSync( - path.join(__dirname, "../dist/aos-bundled.lua"), - "utf-8", + path.join(__dirname, '../dist/aos-bundled.lua'), + 'utf-8', ); - const wallet = fs.readFileSync(path.join(__dirname, "key.json"), "utf-8"); + const wallet = fs.readFileSync(path.join(__dirname, 'key.json'), 'utf-8'); const jwk = JSON.parse(wallet); const address = await arweave.wallets.jwkToAddress(jwk); console.log(`Publish AOS Lua with address ${address}`); const tx = await arweave.createTransaction({ data: bundledLua }, jwk); - tx.addTag("App-Name", "aos-LUA"); - tx.addTag("App-Version", "0.0.1"); - tx.addTag("Content-Type", "text/x-lua"); - tx.addTag("Author", "INSERT AUTHOR NAME"); + tx.addTag('App-Name', 'aos-LUA'); + tx.addTag('App-Version', '0.0.1'); + tx.addTag('Content-Type', 'text/x-lua'); + tx.addTag('Author', 'INSERT AUTHOR NAME'); await arweave.transactions.sign(tx, jwk); await arweave.transactions.post(tx); - console.log("Transaction ID:", tx.id); + console.log('Transaction ID:', tx.id); } main(); diff --git a/tools/scripts/install-deps.js b/tools/scripts/install-deps.js index d08b2b1..5224955 100644 --- a/tools/scripts/install-deps.js +++ b/tools/scripts/install-deps.js @@ -1,20 +1,20 @@ // initialize git submodules and install the ao dev cli -import { exec } from "child_process"; -import { promisify } from "util"; +import { exec } from 'child_process'; +import { promisify } from 'util'; const execPromise = promisify(exec); async function installDeps() { try { // Initialize git submodules - await execPromise("git submodule update --init --recursive"); - console.log("Successfully initialized git submodules"); + await execPromise('git submodule update --init --recursive'); + console.log('Successfully initialized git submodules'); // Install the AO Dev CLI - await execPromise("curl -L https://install_ao.g8way.io | bash"); - console.log("Successfully installed the AO Dev CLI"); + await execPromise('curl -L https://install_ao.g8way.io | bash'); + console.log('Successfully installed the AO Dev CLI'); } catch (err) { - console.error("Error installing dependencies:", err); + console.error('Error installing dependencies:', err); process.exit(1); } } diff --git a/tools/spawn-aos.js b/tools/spawn-aos.js index 73185aa..aeb1b2f 100644 --- a/tools/spawn-aos.js +++ b/tools/spawn-aos.js @@ -1,23 +1,23 @@ -import fs from "fs"; -import path from "path"; -import { createDataItemSigner, connect } from "@permaweb/aoconnect"; -import { fileURLToPath } from "url"; +import { connect, createDataItemSigner } from '@permaweb/aoconnect'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ao = connect({ - GATEWAY_URL: "https://arweave.net", + GATEWAY_URL: 'https://arweave.net', }); -const moduleId = "cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk"; -const scheduler = "_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA"; +const moduleId = 'cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk'; +const scheduler = '_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA'; async function main() { const luaCode = fs.readFileSync( - path.join(__dirname, "../dist/aos-bundled.lua"), - "utf-8", + path.join(__dirname, '../dist/aos-bundled.lua'), + 'utf-8', ); - const wallet = fs.readFileSync(path.join(__dirname, "key.json"), "utf-8"); + const wallet = fs.readFileSync(path.join(__dirname, 'key.json'), 'utf-8'); const signer = createDataItemSigner(JSON.parse(wallet)); const processId = await ao.spawn({ @@ -26,12 +26,12 @@ async function main() { signer, }); - console.log("Process ID:", processId); - console.log("Waiting 20 seconds to ensure process is readied."); + console.log('Process ID:', processId); + console.log('Waiting 20 seconds to ensure process is readied.'); await new Promise((resolve) => setTimeout(resolve, 20_000)); - console.log("Loading ANT Lua code..."); + console.log('Loading ANT Lua code...'); - const testCases = [["Eval", {}, luaCode]]; + const testCases = [['Eval', {}, luaCode]]; for (const [method, args, data] of testCases) { const tags = args @@ -40,7 +40,7 @@ async function main() { const result = await ao .message({ process: processId, - tags: [...tags, { name: "Action", value: method }], + tags: [...tags, { name: 'Action', value: method }], data, signer, }) diff --git a/tools/utils.js b/tools/utils.js index 3d6a108..95adeb3 100644 --- a/tools/utils.js +++ b/tools/utils.js @@ -1,4 +1,5 @@ -import AoLoader from "@permaweb/ao-loader"; +import AoLoader from '@permaweb/ao-loader'; + import { AOS_WASM, AO_LOADER_HANDLER_ENV, @@ -6,7 +7,7 @@ import { BUNDLED_CHESS_GAME_AOS_LUA, BUNDLED_CHESS_REGISTRY_AOS_LUA, DEFAULT_HANDLE_OPTIONS, -} from "./constants.js"; +} from './constants.js'; /** * Loads the aos wasm binary and returns the handle function with program memory @@ -20,7 +21,7 @@ export async function createAosLoader(lua) { null, { ...DEFAULT_HANDLE_OPTIONS, - Tags: [{ name: "Action", value: "Eval" }], + Tags: [{ name: 'Action', value: 'Eval' }], Data: lua, }, AO_LOADER_HANDLER_ENV, @@ -39,7 +40,9 @@ export async function createChessGameAosLoader() { return createAosLoader(BUNDLED_CHESS_GAME_AOS_LUA); } -export async function getHandlers(sendMessage, memory){ - - return sendMessage({Tags: [{name: "Action", value: "Eval"}], Data: "Handlers.list"}, memory) +export async function getHandlers(sendMessage, memory) { + return sendMessage( + { Tags: [{ name: 'Action', value: 'Eval' }], Data: 'Handlers.list' }, + memory, + ); } diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/repl.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/repl.js index 86da13b..bde44b0 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/repl.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/repl.js @@ -1,7 +1,7 @@ -const readline = require("readline"); -const AoLoader = require("@permaweb/ao-loader"); -const fs = require("fs"); -const wasm = fs.readFileSync("./process.wasm"); +const readline = require('readline'); +const AoLoader = require('@permaweb/ao-loader'); +const fs = require('fs'); +const wasm = fs.readFileSync('./process.wasm'); const rl = readline.createInterface({ input: process.stdin, @@ -10,19 +10,19 @@ const rl = readline.createInterface({ const env = { Process: { - Id: "PROCESS_TEST", - Owner: "TOM", + Id: 'PROCESS_TEST', + Owner: 'TOM', }, }; -let prompt = "aos"; +let prompt = 'aos'; async function repl(state) { const handle = await AoLoader(wasm); - rl.question(prompt + "> ", async function (line) { + rl.question(prompt + '> ', async function (line) { // Exit the REPL if the user types "exit" - if (line === "exit") { - console.log("Exiting..."); + if (line === 'exit') { + console.log('Exiting...'); rl.close(); return; } @@ -43,7 +43,7 @@ async function repl(state) { // Continue the REPL await repl(response.buffer); } catch (err) { - console.log("Error:", err); + console.log('Error:', err); process.exit(0); } }); @@ -53,14 +53,14 @@ repl(null); function createMessage(expr) { return { - Owner: "TOM", - Target: "PROCESS", + Owner: 'TOM', + Target: 'PROCESS', Tags: [ - { name: "Data-Protocol", value: "ao" }, - { name: "Variant", value: "ao.TN.1" }, - { name: "Type", value: "message" }, - { name: "function", value: "eval" }, - { name: "expression", value: expr }, + { name: 'Data-Protocol', value: 'ao' }, + { name: 'Variant', value: 'ao.TN.1' }, + { name: 'Type', value: 'message' }, + { name: 'function', value: 'eval' }, + { name: 'expression', value: expr }, ], }; } diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/assignment.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/assignment.test.js index f30f0bd..7bd4180 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/assignment.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/assignment.test.js @@ -1,35 +1,34 @@ -import { describe, test } from "node:test"; -import * as assert from "node:assert"; -import fs from "node:fs"; +import AoLoader from '@permaweb/ao-loader'; +import * as assert from 'node:assert'; +import fs from 'node:fs'; +import { describe, test } from 'node:test'; -import AoLoader from "@permaweb/ao-loader"; - -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; -describe("add the assignable MatchSpec", async () => { - test("by name", async () => { +describe('add the assignable MatchSpec', async () => { + test('by name', async () => { const handle = await AoLoader(wasm, options); const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Thomas' }, ], Data: ` ao.addAssignable('foobar', function (msg) return true end) @@ -41,24 +40,24 @@ describe("add the assignable MatchSpec", async () => { assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { length: 1, - name: "foobar", + name: 'foobar', }); }); - test("update by name", async () => { + test('update by name', async () => { const handle = await AoLoader(wasm, options); const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Thomas' }, ], Data: ` ao.addAssignable('foobar', function (msg) return true end) @@ -71,24 +70,24 @@ describe("add the assignable MatchSpec", async () => { assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { length: 1, - name: "foobar", + name: 'foobar', }); }); - test("by index", async () => { + test('by index', async () => { const handle = await AoLoader(wasm, options); const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Thomas' }, ], Data: ` ao.addAssignable(function (msg) return true end) @@ -101,20 +100,20 @@ describe("add the assignable MatchSpec", async () => { assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { length: 1 }); }); - test("require name to be a string", async () => { + test('require name to be a string', async () => { const handle = await AoLoader(wasm, options); const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Thomas' }, ], Data: ` ao.addAssignable(1234, function (msg) return true end) @@ -123,25 +122,25 @@ describe("add the assignable MatchSpec", async () => { const result = await handle(null, msg, env); - assert.ok(result.Error.includes("MatchSpec name MUST be a string")); + assert.ok(result.Error.includes('MatchSpec name MUST be a string')); }); }); -describe("remove the assignable MatchSpec", () => { - test("by name", async () => { +describe('remove the assignable MatchSpec', () => { + test('by name', async () => { const handle = await AoLoader(wasm, options); const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Thomas' }, ], Data: ` ao.addAssignable(function (msg) return true end) @@ -157,25 +156,25 @@ describe("remove the assignable MatchSpec", () => { assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { length: 2, - name: "foobar", + name: 'foobar', }); assert.deepStrictEqual(JSON.parse(result.Messages[1].Data), { length: 1 }); }); - test("by index", async () => { + test('by index', async () => { const handle = await AoLoader(wasm, options); const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Thomas' }, ], Data: ` ao.addAssignable(function (msg) return true end) @@ -191,28 +190,28 @@ describe("remove the assignable MatchSpec", () => { assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { length: 2, - name: "foobar", + name: 'foobar', }); assert.deepStrictEqual(JSON.parse(result.Messages[1].Data), { length: 1, - name: "foobar", + name: 'foobar', }); }); - test("require name to be a string or number", async () => { + test('require name to be a string or number', async () => { const handle = await AoLoader(wasm, options); const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Thomas' }, ], Data: ` ao.removeAssignable({}) @@ -220,24 +219,24 @@ describe("remove the assignable MatchSpec", () => { }; const result = await handle(null, msg, env); - assert.ok(result.Error.includes("index MUST be a number")); + assert.ok(result.Error.includes('index MUST be a number')); }); }); -describe("determine whether the msg is an assignment or not", () => { - test("is an assignment", async () => { +describe('determine whether the msg is an assignment or not', () => { + test('is an assignment', async () => { const handle = await AoLoader(wasm, options); const addAssignableMsg = { - Target: "AOS", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'AOS', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Thomas' }, ], Data: ` ao.addAssignable(function (msg) return true end) @@ -254,39 +253,39 @@ describe("determine whether the msg is an assignment or not", () => { const { Memory } = await handle(null, addAssignableMsg, env); const msg = { - Target: "NOT_AOS", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'NOT_AOS', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "IsAssignment" }, - { name: "Name", value: "Thomas" }, + { name: 'Action', value: 'IsAssignment' }, + { name: 'Name', value: 'Thomas' }, ], - Data: "foobar", + Data: 'foobar', }; const result = await handle(Memory, msg, env); assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { - id: "1234xyxfoo", + id: '1234xyxfoo', isAssignment: true, }); }); - test("is NOT an assignment", async () => { + test('is NOT an assignment', async () => { const handle = await AoLoader(wasm, options); const addAssignableMsg = { - Target: "AOS", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'AOS', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Thomas' }, ], Data: ` ao.addAssignable(function (msg) return true end) @@ -303,42 +302,42 @@ describe("determine whether the msg is an assignment or not", () => { const { Memory } = await handle(null, addAssignableMsg, env); const msg = { - Target: "AOS", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'AOS', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "IsAssignment" }, - { name: "Name", value: "Thomas" }, + { name: 'Action', value: 'IsAssignment' }, + { name: 'Name', value: 'Thomas' }, ], - Data: "foobar", + Data: 'foobar', }; const result = await handle(Memory, msg, env); assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { - id: "1234xyxfoo", + id: '1234xyxfoo', isAssignment: false, }); }); }); -describe("run handles on assignment based on assignables configured", () => { - test("at least 1 assignable allows specific assignment", async () => { +describe('run handles on assignment based on assignables configured', () => { + test('at least 1 assignable allows specific assignment', async () => { const handle = await AoLoader(wasm, options); const addAssignableMsg = { - Target: "AOS", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'AOS', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Thomas' }, ], Data: ` ao.addAssignable(function (msg) return msg.Name == 'Frank' end) @@ -349,36 +348,36 @@ describe("run handles on assignment based on assignables configured", () => { const { Memory } = await handle(null, addAssignableMsg, env); const msg = { - Target: "NOT_AOS", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'NOT_AOS', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Thomas' }, ], - Data: "2 + 2", + Data: '2 + 2', }; const result = await handle(Memory, msg, env); - assert.deepStrictEqual(result.Output.data, "4"); + assert.deepStrictEqual(result.Output.data, '4'); }); - test("assignables do NOT allow specific assignment", async () => { + test('assignables do NOT allow specific assignment', async () => { const handle = await AoLoader(wasm, options); const addAssignableMsg = { - Target: "AOS", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'AOS', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Thomas' }, ], Data: ` ao.addAssignable(function (msg) return msg.Name == 'Frank' end) @@ -389,39 +388,39 @@ describe("run handles on assignment based on assignables configured", () => { const { Memory } = await handle(null, addAssignableMsg, env); const msg = { - Target: "NOT_AOS", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'NOT_AOS', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Not-Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Not-Thomas' }, ], - Data: "2 + 2", + Data: '2 + 2', }; const result = await handle(Memory, msg, env); assert.deepStrictEqual( result.Messages[0].Data, - "Assignment is not trusted by this process!", + 'Assignment is not trusted by this process!', ); }); - test("assignable does NOT allow specific assignment", async () => { + test('assignable does NOT allow specific assignment', async () => { const handle = await AoLoader(wasm, options); const addAssignableMsg = { - Target: "AOS", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'AOS', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Thomas' }, ], Data: ` ao.addAssignable(function (msg) return msg.Name == 'Thomas' end) @@ -431,47 +430,47 @@ describe("run handles on assignment based on assignables configured", () => { const { Memory } = await handle(null, addAssignableMsg, env); const msg = { - Target: "NOT_AOS", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'NOT_AOS', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Not-Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Not-Thomas' }, ], - Data: "2 + 2", + Data: '2 + 2', }; const result = await handle(Memory, msg, env); assert.deepStrictEqual( result.Messages[0].Data, - "Assignment is not trusted by this process!", + 'Assignment is not trusted by this process!', ); }); - test("no assignables defaults to no assignments allowed", async () => { + test('no assignables defaults to no assignments allowed', async () => { const handle = await AoLoader(wasm, options); const msg = { - Target: "NOT_AOS", - Owner: "FOOBAR", - "Block-Height": "1000", - Id: "1234xyxfoo", - From: "FOOBAR", - Module: "WOOPAWOOPA", + Target: 'NOT_AOS', + Owner: 'FOOBAR', + 'Block-Height': '1000', + Id: '1234xyxfoo', + From: 'FOOBAR', + Module: 'WOOPAWOOPA', Tags: [ - { name: "Action", value: "Eval" }, - { name: "Name", value: "Not-Thomas" }, + { name: 'Action', value: 'Eval' }, + { name: 'Name', value: 'Not-Thomas' }, ], - Data: "2 + 2", + Data: '2 + 2', }; const result = await handle(null, msg, env); assert.deepStrictEqual( result.Messages[0].Data, - "Assignment is not trusted by this process!", + 'Assignment is not trusted by this process!', ); }); }); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/aes.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/aes.test.js index 82719a6..ca0a51c 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/aes.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/aes.test.js @@ -1,40 +1,40 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); +const wasm = fs.readFileSync('./process.wasm'); const options = { - format: "wasm64-unknown-emscripten-draft_2024_02_15", + format: 'wasm64-unknown-emscripten-draft_2024_02_15', computeLimit: 10024704733, }; -test("run aes cipher successfully", async () => { +test('run aes cipher successfully', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const results = [ // AES128 CBC Mode - "A3B9E6E1FBD9D46930E5F76807C84B8E", - "616F0000000000000000000000000000", + 'A3B9E6E1FBD9D46930E5F76807C84B8E', + '616F0000000000000000000000000000', // AES128 ECB Mode - "3FF54BD61AD1AA06BC367A10575CC7C5", - "616F0000000000000000000000000000", + '3FF54BD61AD1AA06BC367A10575CC7C5', + '616F0000000000000000000000000000', // AES128 CFB Mode - "1DA7169C093D6B23160B6785B28E4BED", - "616F0000000000000000000000000000", + '1DA7169C093D6B23160B6785B28E4BED', + '616F0000000000000000000000000000', // AES128 OFB Mode - "1DA7169C093D6B23160B6785B28E4BED", - "616F0000000000000000000000000000", + '1DA7169C093D6B23160B6785B28E4BED', + '616F0000000000000000000000000000', // AES128 CTR Mode - "1DA7169C093D6B23160B6785B28E4BED", - "616F0000000000000000000000000000", + '1DA7169C093D6B23160B6785B28E4BED', + '616F0000000000000000000000000000', ]; const data = ` @@ -77,19 +77,19 @@ test("run aes cipher successfully", async () => { return table.concat(results, ", ") `; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: data, }; const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results.join(", ")); + assert.equal(result.Output?.data, results.join(', ')); // assert.ok(result.GasUsed >= 3000000000) assert.ok(true); }); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/issac.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/issac.test.js index 6beea27..c87ab5c 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/issac.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/issac.test.js @@ -1,21 +1,21 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; - -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; -test("run issac cipher successfully", async () => { +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; + +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; +test('run issac cipher successfully', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; - const results = ["7851", "ao"]; + const results = ['7851', 'ao']; const data = ` local crypto = require(".crypto"); @@ -36,17 +36,17 @@ test("run issac cipher successfully", async () => { return table.concat(results, ", "); `; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: data, }; const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results.join(", ")); + assert.equal(result.Output?.data, results.join(', ')); assert.ok(true); }); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/morus.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/morus.test.js index 90ffc7e..4748743 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/morus.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/morus.test.js @@ -1,26 +1,26 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); +const wasm = fs.readFileSync('./process.wasm'); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; -test("run morus cipher successfully", async () => { +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; +test('run morus cipher successfully', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const results = [ - "514ed31473d8fb0b76c6cbb17af35ed01d0a", - "ao", - "6164646974696f6e616c20646174616aae7a8b95c50047bea251c3b7133eec5fcc", - "ao", + '514ed31473d8fb0b76c6cbb17af35ed01d0a', + 'ao', + '6164646974696f6e616c20646174616aae7a8b95c50047bea251c3b7133eec5fcc', + 'ao', ]; const data = ` @@ -55,17 +55,17 @@ test("run morus cipher successfully", async () => { return table.concat(results, ", "); `; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: data, }; const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results.join(", ")); + assert.equal(result.Output?.data, results.join(', ')); assert.ok(true); }); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/norx.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/norx.test.js index b5d5cb1..473922a 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/norx.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/norx.test.js @@ -1,28 +1,28 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); +const wasm = fs.readFileSync('./process.wasm'); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; -test("run norx cipher successfully", async () => { +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; +test('run norx cipher successfully', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const results = [ // encrypted cipher - "0bb35a06938e6541eccd4440adb7b46118535f60b09b4adf378807a53df19fc4ea28", + '0bb35a06938e6541eccd4440adb7b46118535f60b09b4adf378807a53df19fc4ea28', // auth tag - "5a06938e6541eccd4440adb7b46118535f60b09b4adf378807a53df19fc4ea28", + '5a06938e6541eccd4440adb7b46118535f60b09b4adf378807a53df19fc4ea28', // decrypted value - "ao", + 'ao', ]; const data = ` @@ -53,17 +53,17 @@ test("run norx cipher successfully", async () => { return table.concat(results, ", ") `; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: data, }; const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results.join(", ")); + assert.equal(result.Output?.data, results.join(', ')); assert.ok(true); }); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/blake2b.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/blake2b.test.js index bd1f111..b57df1e 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/blake2b.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/blake2b.test.js @@ -1,23 +1,23 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; - -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; -test("run sha3 hash successfully", async () => { +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; + +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; +test('run sha3 hash successfully', async () => { const results = [ - "576701fd79a126f2c414ef94adf1117c88943700f312679d018c29c378b2c807a3412b4e8d51e191c48fb5f5f54bf1bca29a714dda166797b3baf9ead862ae1d", - "7050811afc947ba7190bb3c0a7b79b4fba304a0de61d529c8a35bdcbbb5544f4", - "203c101980fdf6cf24d78879f2e3db86d73d91f7d60960b642022cd6f87408f8", + '576701fd79a126f2c414ef94adf1117c88943700f312679d018c29c378b2c807a3412b4e8d51e191c48fb5f5f54bf1bca29a714dda166797b3baf9ead862ae1d', + '7050811afc947ba7190bb3c0a7b79b4fba304a0de61d529c8a35bdcbbb5544f4', + '203c101980fdf6cf24d78879f2e3db86d73d91f7d60960b642022cd6f87408f8', ]; const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; @@ -34,17 +34,17 @@ test("run sha3 hash successfully", async () => { return table.concat(results, ", "); `; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: data, }; const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results.join(", ")); + assert.equal(result.Output?.data, results.join(', ')); assert.ok(true); }); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md2.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md2.test.js index 46aad07..ced036a 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md2.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md2.test.js @@ -1,26 +1,26 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); +const wasm = fs.readFileSync('./process.wasm'); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test("run md2 hash successfully", async () => { +test('run md2 hash successfully', async () => { const cases = [ - ["", "8350e5a3e24c153df2275c9f80692773"], - ["ao", "0d4e80edd07bee6c7965b21b25a9b1ea"], - ["abc", "da853b0d3f88d99b30283a69e6ded6bb"], - ["abcdefghijklmnopqrstuvwxyz", "4e8ddff3650292ab5a4108c3aa47940b"], - ["Hello World!", "315f7c67223f01fb7cab4b95100e872e"], + ['', '8350e5a3e24c153df2275c9f80692773'], + ['ao', '0d4e80edd07bee6c7965b21b25a9b1ea'], + ['abc', 'da853b0d3f88d99b30283a69e6ded6bb'], + ['abcdefghijklmnopqrstuvwxyz', '4e8ddff3650292ab5a4108c3aa47940b'], + ['Hello World!', '315f7c67223f01fb7cab4b95100e872e'], ]; const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const testCase = async (e) => { @@ -31,13 +31,13 @@ test("run md2 hash successfully", async () => { return crypto.digest.md2(str).asHex(); `; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: data, }; diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md4.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md4.test.js index 26b4945..a52bc31 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md4.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md4.test.js @@ -1,25 +1,25 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); +const wasm = fs.readFileSync('./process.wasm'); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; -test("run md4 hash successfully", async () => { +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; +test('run md4 hash successfully', async () => { const cases = [ - ["", "31d6cfe0d16ae931b73c59d7e0c089c0"], - ["ao", "e068dfe3d8cb95311b58be566db66954"], - ["abc", "a448017aaf21d8525fc10ae87aa6729d"], - ["abcdefghijklmnopqrstuvwxyz", "d79e1c308aa5bbcdeea8ed63df412da9"], - ["Hello World!", "b2a5cc34fc21a764ae2fad94d56fadf6"], + ['', '31d6cfe0d16ae931b73c59d7e0c089c0'], + ['ao', 'e068dfe3d8cb95311b58be566db66954'], + ['abc', 'a448017aaf21d8525fc10ae87aa6729d'], + ['abcdefghijklmnopqrstuvwxyz', 'd79e1c308aa5bbcdeea8ed63df412da9'], + ['Hello World!', 'b2a5cc34fc21a764ae2fad94d56fadf6'], ]; const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; @@ -31,13 +31,13 @@ test("run md4 hash successfully", async () => { return crypto.digest.md4(str).asHex(); `; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: data, }; diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md5.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md5.test.js index a7f4e71..dbc8844 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md5.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md5.test.js @@ -1,25 +1,25 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test("run md5 hash successfully", async () => { +test('run md5 hash successfully', async () => { const cases = [ - ["", "d41d8cd98f00b204e9800998ecf8427e"], - ["ao", "adac5e63f80f8629e9573527b25891d3"], - ["abc", "900150983cd24fb0d6963f7d28e17f72"], - ["abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b"], - ["Hello World!", "ed076287532e86365e841e92bfc50d8c"], + ['', 'd41d8cd98f00b204e9800998ecf8427e'], + ['ao', 'adac5e63f80f8629e9573527b25891d3'], + ['abc', '900150983cd24fb0d6963f7d28e17f72'], + ['abcdefghijklmnopqrstuvwxyz', 'c3fcd3d76192e4007dfb496cca67e13b'], + ['Hello World!', 'ed076287532e86365e841e92bfc50d8c'], ]; const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; @@ -31,13 +31,13 @@ test("run md5 hash successfully", async () => { return crypto.digest.md5(str).asHex(); `; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: data, }; diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha1.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha1.test.js index 0f2f69a..a0236cb 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha1.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha1.test.js @@ -1,24 +1,24 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; -test("run sha1 hash successfully", async () => { +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; +test('run sha1 hash successfully', async () => { const cases = [ - ["", "da39a3ee5e6b4b0d3255bfef95601890afd80709"], - ["ao", "c29dd6c83b67a1d6d3b28588a1f068b68689aa1d"], - ["abc", "a9993e364706816aba3e25717850c26c9cd0d89d"], - ["abcdefghijklmnopqrstuvwxyz", "32d10c7b8cf96570ca04ce37f2a19d84240d3a89"], - ["Hello World!", "2ef7bde608ce5404e97d5f042f95f89f1c232871"], + ['', 'da39a3ee5e6b4b0d3255bfef95601890afd80709'], + ['ao', 'c29dd6c83b67a1d6d3b28588a1f068b68689aa1d'], + ['abc', 'a9993e364706816aba3e25717850c26c9cd0d89d'], + ['abcdefghijklmnopqrstuvwxyz', '32d10c7b8cf96570ca04ce37f2a19d84240d3a89'], + ['Hello World!', '2ef7bde608ce5404e97d5f042f95f89f1c232871'], ]; const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; @@ -30,13 +30,13 @@ test("run sha1 hash successfully", async () => { return crypto.digest.sha1(str).asHex(); `; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: data, }; diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha2.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha2.test.js index d295bcd..94f42f3 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha2.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha2.test.js @@ -1,21 +1,21 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; -test("run sha2 hash successfully", async () => { +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; +test('run sha2 hash successfully', async () => { const results = [ - "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", - "6f36a696b17ce5a71efa700e8a7e47994f3e134a5e5f387b3e7c2c912abe94f94ee823f9b9dcae59af99e2e34c8b4fb0bd592260c6720ee49e5deaac2065c4b1", + 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', + '6f36a696b17ce5a71efa700e8a7e47994f3e134a5e5f387b3e7c2c912abe94f94ee823f9b9dcae59af99e2e34c8b4fb0bd592260c6720ee49e5deaac2065c4b1', ]; const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; @@ -34,17 +34,17 @@ test("run sha2 hash successfully", async () => { return table.concat(results, ", "); `; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: data, }; const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results.join(", ")); + assert.equal(result.Output?.data, results.join(', ')); assert.ok(true); }); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha3.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha3.test.js index e49cb29..0267adc 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha3.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha3.test.js @@ -1,24 +1,24 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; -test("run sha3 hash successfully", async () => { +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; +test('run sha3 hash successfully', async () => { const results = [ - "1bbe785577db997a394d5b4555eec9159cb51f235aec07514872d2d436c6e985", - "0c29f053400cb1764ce2ec555f598f497e6fcd1d304ce0125faa03bb724f63f213538f41103072ff62ddee701b52c73e621ed4d2254a3e5e9a803d83435b704d", - "76da52eec05b749b99d6e62bb52333c1569fe75284e6c82f3de12a4618be00d6", - "046fbfad009a12cef9ff00c2aac361d004347b2991c1fa80fba5582251b8e0be8def0283f45f020d4b04ff03ead9f6e7c43cc3920810c05b33b4873b99affdea", + '1bbe785577db997a394d5b4555eec9159cb51f235aec07514872d2d436c6e985', + '0c29f053400cb1764ce2ec555f598f497e6fcd1d304ce0125faa03bb724f63f213538f41103072ff62ddee701b52c73e621ed4d2254a3e5e9a803d83435b704d', + '76da52eec05b749b99d6e62bb52333c1569fe75284e6c82f3de12a4618be00d6', + '046fbfad009a12cef9ff00c2aac361d004347b2991c1fa80fba5582251b8e0be8def0283f45f020d4b04ff03ead9f6e7c43cc3920810c05b33b4873b99affdea', ]; const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; @@ -35,17 +35,17 @@ test("run sha3 hash successfully", async () => { return table.concat(results,", ") `; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: data, }; const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results.join(", ")); + assert.equal(result.Output?.data, results.join(', ')); assert.ok(true); }); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/kdf/pbkdf2.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/kdf/pbkdf2.test.js index 5ccaa74..455065d 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/kdf/pbkdf2.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/kdf/pbkdf2.test.js @@ -1,19 +1,19 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test("run pbkdf2 successfully", async () => { - const results = ["C4C21BF2BBF61541408EC2A49C89B9C6"]; +test('run pbkdf2 successfully', async () => { + const results = ['C4C21BF2BBF61541408EC2A49C89B9C6']; const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; @@ -32,13 +32,13 @@ test("run pbkdf2 successfully", async () => { return out.asHex() `; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: data, }; diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/mac/hmac.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/mac/hmac.test.js index fa2f733..9f1e004 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/mac/hmac.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/mac/hmac.test.js @@ -1,23 +1,23 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; - -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; -test("run hmac successfully", async () => { +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; + +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; +test('run hmac successfully', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const results = [ - "3966f45acb53f7a1a493bae15afecb1a204fa32d", - "542da02a324155d688c7689669ff94c6a5f906892aa8eccd7284f210ac66e2a7", + '3966f45acb53f7a1a493bae15afecb1a204fa32d', + '542da02a324155d688c7689669ff94c6a5f906892aa8eccd7284f210ac66e2a7', ]; const data = ` @@ -34,17 +34,17 @@ test("run hmac successfully", async () => { return table.concat(results, ", ") `; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: data, }; const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results.join(", ")); + assert.equal(result.Output?.data, results.join(', ')); assert.ok(true); }); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/random.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/random.test.js index c60a11a..59c0302 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/random.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/random.test.js @@ -1,17 +1,17 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; -test("run random generator successfully", async () => { +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; +test('run random generator successfully', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; @@ -21,13 +21,13 @@ test("run random generator successfully", async () => { return crypto.random(); `; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: data, }; diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/eval.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/eval.test.js index 9bc130d..b9eae61 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/eval.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/eval.test.js @@ -1,107 +1,107 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; -test("run evaluate action unsuccessfully", async () => { +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; +test('run evaluate action unsuccessfully', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], - Data: "100 < undefined", + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], + Data: '100 < undefined', }; const result = await handle(null, msg, env); - assert.ok(result.Error.includes("attempt to compare number with nil")); + assert.ok(result.Error.includes('attempt to compare number with nil')); assert.ok(true); }); -test("run evaluate action successfully", async () => { +test('run evaluate action successfully', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], - Data: "1 + 1", + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], + Data: '1 + 1', }; const result = await handle(null, msg, env); - assert.equal(result.Output?.data, "2"); + assert.equal(result.Output?.data, '2'); assert.ok(true); }); -test("print hello world", async () => { +test('print hello world', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: `print("Hello World")`, }; const result = await handle(null, msg, env); - assert.equal(result.Output?.data, "Hello World"); + assert.equal(result.Output?.data, 'Hello World'); assert.ok(true); }); -test("create an Assignment", async () => { +test('create an Assignment', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", + Id: 'AOS', - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: 'Assign({ Processes = { "pid-1", "pid-2" }, Message = "mid-1" })', }; const result = await handle(null, msg, env); assert.deepStrictEqual(result.Assignments, [ - { Processes: ["pid-1", "pid-2"], Message: "mid-1" }, + { Processes: ['pid-1', 'pid-2'], Message: 'mid-1' }, ]); assert.ok(true); }); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/handlers.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/handlers.test.js index 7d765bf..8f1c97f 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/handlers.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/handlers.test.js @@ -1,28 +1,28 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test("handlers receive", async () => { +test('handlers receive', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: ` local msg = ao.send({Target = ao.id, Data = "Hello"}) local res = Handlers.receive({From = msg.Target, ['X-Reference'] = msg.Ref_}) @@ -37,39 +37,39 @@ return require('json').encode(res) console.log(Messages[0]); // --- const m = { - Target: "AOS", - From: "FRED", - Owner: "FRED", + Target: 'AOS', + From: 'FRED', + Owner: 'FRED', Tags: [ { - name: "X-Reference", - value: "1", + name: 'X-Reference', + value: '1', }, ], - Data: "test receive", + Data: 'test receive', }; const result = await handle(Memory, m, env); console.log(result.Output); assert.ok(true); }); -test("resolvers", async () => { +test('resolvers', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: ` Handlers.once("onetime", { @@ -86,34 +86,34 @@ Handlers.once("onetime", const { Memory } = await handle(null, msg, env); // --- const ping = { - Target: "AOS", - From: "FRED", - Owner: "FRED", - Tags: [{ name: "Action", value: "ping" }], - Data: "ping", + Target: 'AOS', + From: 'FRED', + Owner: 'FRED', + Tags: [{ name: 'Action', value: 'ping' }], + Data: 'ping', }; const result = await handle(Memory, ping, env); // handled once - assert.equal(result.Output.data, "pong"); + assert.equal(result.Output.data, 'pong'); }); -test("handlers once", async () => { +test('handlers once', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: ` Handlers.once("onetime", Handlers.utils.hasMatchingData("ping"), @@ -127,38 +127,38 @@ Handlers.once("onetime", const { Memory } = await handle(null, msg, env); // --- const ping = { - From: "FRED", - Target: "AOS", - Owner: "FRED", + From: 'FRED', + Target: 'AOS', + Owner: 'FRED', Tags: [], - Data: "ping", + Data: 'ping', }; const result = await handle(Memory, ping, env); // handled once - assert.equal(result.Output.data, "pong"); + assert.equal(result.Output.data, 'pong'); const result2 = await handle(result.Memory, ping, env); // not handled - assert.ok(result2.Output.data.includes("New Message From")); + assert.ok(result2.Output.data.includes('New Message From')); }); -test("ping pong", async () => { +test('ping pong', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: ` Handlers.add("ping", Handlers.utils.hasMatchingData("ping"), @@ -172,34 +172,34 @@ Handlers.add("ping", const { Memory } = await handle(null, msg, env); // --- const ping = { - Target: "AOS", - From: "FRED", - Owner: "FRED", + Target: 'AOS', + From: 'FRED', + Owner: 'FRED', Tags: [], - Data: "ping", + Data: 'ping', }; const result = await handle(Memory, ping, env); - assert.equal(result.Output.data, "pong"); + assert.equal(result.Output.data, 'pong'); assert.ok(true); }); -test("handler pipeline", async () => { +test('handler pipeline', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: ` Handlers.add("one", function (Msg) @@ -232,37 +232,37 @@ Handlers.add("three", const { Memory } = await handle(null, msg, env); // --- const ping = { - Target: "AOS", - From: "FRED", - Owner: "FRED", + Target: 'AOS', + From: 'FRED', + Owner: 'FRED', Tags: [], - Data: "ping", + Data: 'ping', }; const result = await handle(Memory, ping, env); assert.equal( result.Output.data, - "one\ntwo\n\x1B[90mNew Message From \x1B[32mFRE...RED\x1B[90m: \x1B[90mData = \x1B[34mping\x1B[0m", + 'one\ntwo\n\x1B[90mNew Message From \x1B[32mFRE...RED\x1B[90m: \x1B[90mData = \x1B[34mping\x1B[0m', ); assert.ok(true); }); -test("timestamp", async () => { +test('timestamp', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: ` Handlers.add("timestamp", Handlers.utils.hasMatchingData("timestamp"), @@ -277,11 +277,11 @@ Handlers.add("timestamp", // --- const currentTimestamp = Date.now(); const timestamp = { - Target: "AOS", - From: "FRED", - Owner: "FRED", + Target: 'AOS', + From: 'FRED', + Owner: 'FRED', Tags: [], - Data: "timestamp", + Data: 'timestamp', Timestamp: currentTimestamp, }; const result = await handle(Memory, timestamp, env); @@ -289,23 +289,23 @@ Handlers.add("timestamp", assert.ok(true); }); -test("test pattern, fn handler", async () => { +test('test pattern, fn handler', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: ` Handlers.add("Balance", function (msg) @@ -319,14 +319,14 @@ Handlers.add("Balance", // --- const currentTimestamp = Date.now(); const balance = { - Target: "AOS", - From: "FRED", - Owner: "FRED", - Tags: [{ name: "Action", value: "Balance" }], - Data: "timestamp", + Target: 'AOS', + From: 'FRED', + Owner: 'FRED', + Tags: [{ name: 'Action', value: 'Balance' }], + Data: 'timestamp', Timestamp: currentTimestamp, }; const result = await handle(Memory, balance, env); - assert.equal(result.Messages[0].Data, "1000"); + assert.equal(result.Messages[0].Data, '1000'); assert.ok(true); }); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/inbox.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/inbox.test.js index 448b37e..ff72b4b 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/inbox.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/inbox.test.js @@ -1,28 +1,28 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test.skip("inbox unbounded", async () => { +test.skip('inbox unbounded', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Data: "Hello", + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Data: 'Hello', Tags: [], }; const result = await handle(null, msg, env); @@ -34,18 +34,18 @@ test.skip("inbox unbounded", async () => { const count = await handle( memory, { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], - Data: "#Inbox", + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], + Data: '#Inbox', }, env, ); //assert.equal(count.Error, 'Error') - assert.equal(count.Output?.data, "10000"); + assert.equal(count.Output?.data, '10000'); assert.ok(true); }); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/magic-table.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/magic-table.test.js index 418ea4a..827d2fd 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/magic-table.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/magic-table.test.js @@ -1,29 +1,29 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test("magictable to wrap send to convert data to json", async () => { +test('magictable to wrap send to convert data to json', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", + Id: 'AOS', - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: 'Send({ Target = "AOS", Data = { foo = "bar" }})', }; const result = await handle(null, msg, env); @@ -33,31 +33,31 @@ test("magictable to wrap send to convert data to json", async () => { const inboxResult = await handle( tableResult.Memory, Object.assign({}, msg, { - Tags: [{ name: "Action", value: "Eval" }], - Data: "Inbox[1].Data.foo", + Tags: [{ name: 'Action', value: 'Eval' }], + Data: 'Inbox[1].Data.foo', }), env, ); - assert.equal(inboxResult.Output.data, "bar"); + assert.equal(inboxResult.Output.data, 'bar'); }); -test("magictable to wrap swap to convert data to json", async () => { +test('magictable to wrap swap to convert data to json', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: 'Spawn("AWESOME_SAUCE", { Target = "TEST", Data = { foo = "bar" }})', }; const result = await handle(null, msg, env); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/print.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/print.test.js index 7c34a71..219e9f0 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/print.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/print.test.js @@ -1,90 +1,90 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test("multi print feature", async () => { +test('multi print feature', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: ` print("one") print("two") `, }; const result = await handle(null, msg, env); - assert.equal(result.Output?.data, "one\ntwo"); + assert.equal(result.Output?.data, 'one\ntwo'); assert.ok(true); }); -test("multi print feature via handler", async () => { +test('multi print feature via handler', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: 'Handlers.add("ping", Handlers.utils.hasMatchingData("ping"), function (m) print(m.Data); print("pong") end)', }; const { Memory } = await handle(null, msg, env); let msg2 = msg; msg2.Tags = []; - msg2.Data = "ping"; + msg2.Data = 'ping'; const result = await handle(Memory, msg2, env); - assert.equal(result.Output.data, "ping\npong"); + assert.equal(result.Output.data, 'ping\npong'); assert.ok(true); }); -test("Typos for functions should generate errors", async () => { +test('Typos for functions should generate errors', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: 'Handers.add("ping", Handlers.utils.hasMatchingData("ping"), function (m) print(m.Data); print("pong") end)', }; const { Memory, Output, Error } = await handle(null, msg, env); let msg2 = msg; - msg2.Tags = [{ name: "Action", value: "Eval" }]; - msg2.Data = "Errors"; + msg2.Tags = [{ name: 'Action', value: 'Eval' }]; + msg2.Data = 'Errors'; const result = await handle(Memory, msg2, env); assert.ok( result.Output.data.includes( @@ -93,32 +93,32 @@ test("Typos for functions should generate errors", async () => { ); }); -test("Print Errors in Handlers", async () => { +test('Print Errors in Handlers', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: 'Handlers.add("ping", Handlers.utils.hasMatchingData("ping"), function (m) print(m.Data); print("pong" .. x) end)', }; const { Memory, Output, Error } = await handle(null, msg, env); let msg2 = msg; msg2.Tags = []; - msg2.Data = "ping"; + msg2.Data = 'ping'; const result = await handle(Memory, msg2, env); - assert.ok(result.Output.data.includes("handling message")); + assert.ok(result.Output.data.includes('handling message')); assert.ok(true); }); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/random.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/random.test.js index 7e4a9af..6f489e7 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/random.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/random.test.js @@ -1,31 +1,31 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test("generate random number", async () => { +test('generate random number', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], - Data: "math.random(10)", + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], + Data: 'math.random(10)', }; const result = await handle(null, msg, env); - assert.equal(result.Output?.data, "9"); + assert.equal(result.Output?.data, '9'); assert.ok(true); }); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/state.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/state.test.js index 0b82172..3d29cfd 100644 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/state.test.js +++ b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/state.test.js @@ -1,63 +1,63 @@ -import { test } from "node:test"; -import * as assert from "node:assert"; -import AoLoader from "@permaweb/ao-loader"; -import fs from "fs"; +import AoLoader from '@permaweb/ao-loader'; +import fs from 'fs'; +import * as assert from 'node:assert'; +import { test } from 'node:test'; -const wasm = fs.readFileSync("./process.wasm"); -const options = { format: "wasm64-unknown-emscripten-draft_2024_02_15" }; +const wasm = fs.readFileSync('./process.wasm'); +const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test("check state properties for aos", async () => { +test('check state properties for aos', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", - Tags: [{ name: "Name", value: "Thomas" }], + Id: 'AOS', + Owner: 'FOOBAR', + Tags: [{ name: 'Name', value: 'Thomas' }], }, }; const msg = { - Target: "AOS", - From: "FOOBAR", - Owner: "FOOBAR", - From: "FOOBAR", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], + Target: 'AOS', + From: 'FOOBAR', + Owner: 'FOOBAR', + From: 'FOOBAR', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], Data: 'print("name: " .. Name .. ", owner: " .. Owner)', }; const result = await handle(null, msg, env); - assert.equal(result.Output?.data, "name: Thomas, owner: FOOBAR"); + assert.equal(result.Output?.data, 'name: Thomas, owner: FOOBAR'); assert.ok(true); }); -test("test authorities", async () => { +test('test authorities', async () => { const handle = await AoLoader(wasm, options); const env = { Process: { - Id: "AOS", - Owner: "FOOBAR", + Id: 'AOS', + Owner: 'FOOBAR', Tags: [ - { name: "Name", value: "Thomas" }, - { name: "Authority", value: "BOOP" }, + { name: 'Name', value: 'Thomas' }, + { name: 'Authority', value: 'BOOP' }, ], }, }; const msg = { - Target: "AOS", - Owner: "BEEP", - From: "BAM", - ["Block-Height"]: "1000", - Id: "1234xyxfoo", - Module: "WOOPAWOOPA", - Tags: [{ name: "Action", value: "Eval" }], - Data: "1 + 1", + Target: 'AOS', + Owner: 'BEEP', + From: 'BAM', + ['Block-Height']: '1000', + Id: '1234xyxfoo', + Module: 'WOOPAWOOPA', + Tags: [{ name: 'Action', value: 'Eval' }], + Data: '1 + 1', }; const result = await handle(null, msg, env); assert.ok( result.Output.data.includes( - "Message is not trusted! From: BAM - Owner: BEEP", + 'Message is not trusted! From: BAM - Owner: BEEP', ), ); }); From fab350012c21165fab498c0980aa9dccd2d9b41d Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Thu, 31 Oct 2024 18:52:23 -0600 Subject: [PATCH 6/9] fix(gitignore): remove dist from git ignore to remove dist --- .gitignore | 1 - dist/registry/aos-bundled.lua | 327 ---- dist/vault/aos-bundled.lua | 188 -- package.json | 1 + tools/bundle-aos.js | 2 - .../aos-process/.gitignore | 7 - .../aos-process/README.md | 21 - .../aos-process/ao.lua | 296 --- .../aos-process/assignment.lua | 98 - .../aos-process/base64.lua | 201 -- .../aos-process/bint.lua | 1739 ----------------- .../aos-process/chance.lua | 86 - .../aos-process/crypto.md | 852 -------- .../aos-process/crypto/cipher/aes.lua | 142 -- .../aos-process/crypto/cipher/aes128.lua | 415 ---- .../aos-process/crypto/cipher/aes192.lua | 462 ----- .../aos-process/crypto/cipher/aes256.lua | 498 ----- .../aos-process/crypto/cipher/init.lua | 14 - .../aos-process/crypto/cipher/issac.lua | 204 -- .../aos-process/crypto/cipher/mode/cbc.lua | 171 -- .../aos-process/crypto/cipher/mode/cfb.lua | 171 -- .../aos-process/crypto/cipher/mode/ctr.lua | 252 --- .../aos-process/crypto/cipher/mode/ecb.lua | 156 -- .../aos-process/crypto/cipher/mode/ofb.lua | 172 -- .../aos-process/crypto/cipher/morus.lua | 398 ---- .../aos-process/crypto/cipher/norx.lua | 321 --- .../aos-process/crypto/digest/blake2b.lua | 190 -- .../aos-process/crypto/digest/init.lua | 29 - .../aos-process/crypto/digest/md2.lua | 112 -- .../aos-process/crypto/digest/md4.lua | 193 -- .../aos-process/crypto/digest/md5.lua | 178 -- .../aos-process/crypto/digest/sha1.lua | 191 -- .../aos-process/crypto/digest/sha2_256.lua | 230 --- .../aos-process/crypto/digest/sha2_512.lua | 107 - .../aos-process/crypto/digest/sha3.lua | 235 --- .../aos-process/crypto/init.lua | 17 - .../aos-process/crypto/kdf/init.lua | 8 - .../aos-process/crypto/kdf/pbkdf2.lua | 159 -- .../aos-process/crypto/mac/hmac.lua | 126 -- .../aos-process/crypto/mac/init.lua | 8 - .../aos-process/crypto/padding/zero.lua | 17 - .../aos-process/crypto/util/array.lua | 222 --- .../aos-process/crypto/util/bit.lua | 44 - .../aos-process/crypto/util/hex.lua | 46 - .../aos-process/crypto/util/init.lua | 16 - .../aos-process/crypto/util/queue.lua | 47 - .../aos-process/crypto/util/stream.lua | 98 - .../aos-process/default.lua | 22 - .../aos-process/dump.lua | 297 --- .../aos-process/eval.lua | 38 - .../aos-process/handlers-utils.lua | 59 - .../aos-process/handlers-utils.md | 138 -- .../aos-process/handlers.lua | 302 --- .../aos-process/handlers.md | 102 - .../aos-process/package.json | 16 - .../aos-process/pretty.lua | 20 - .../aos-process/process.lua | 371 ---- .../aos-process/process.md | 87 - .../aos-process/repl.js | 81 - .../aos-process/stringify.lua | 79 - .../aos-process/test/assignment.test.js | 476 ----- .../test/crypto/cipher/aes.test.js | 95 - .../test/crypto/cipher/issac.test.js | 52 - .../test/crypto/cipher/morus.test.js | 71 - .../test/crypto/cipher/norx.test.js | 69 - .../test/crypto/digest/blake2b.test.js | 50 - .../test/crypto/digest/md2.test.js | 55 - .../test/crypto/digest/md4.test.js | 53 - .../test/crypto/digest/md5.test.js | 53 - .../test/crypto/digest/sha1.test.js | 52 - .../test/crypto/digest/sha2.test.js | 50 - .../test/crypto/digest/sha3.test.js | 51 - .../test/crypto/kdf/pbkdf2.test.js | 48 - .../aos-process/test/crypto/mac/hmac.test.js | 50 - .../aos-process/test/crypto/random.test.js | 37 - .../aos-process/test/eval.test.js | 107 - .../aos-process/test/handlers.test.js | 332 ---- .../aos-process/test/inbox.test.js | 51 - .../aos-process/test/magic-table.test.js | 65 - .../aos-process/test/print.test.js | 124 -- .../aos-process/test/random.test.js | 31 - .../aos-process/test/state.test.js | 63 - .../aos-process/utils.lua | 261 --- .../aos-process/utils.md | 89 - yarn.lock | 48 +- 85 files changed, 48 insertions(+), 13465 deletions(-) delete mode 100644 dist/registry/aos-bundled.lua delete mode 100644 dist/vault/aos-bundled.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/.gitignore delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/README.md delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/ao.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/assignment.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/base64.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/bint.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/chance.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto.md delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes128.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes192.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes256.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/init.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/issac.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cbc.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cfb.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ctr.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ecb.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ofb.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/morus.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/norx.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/blake2b.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/init.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md2.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md4.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md5.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha1.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_256.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_512.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha3.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/init.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/kdf/init.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/kdf/pbkdf2.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/hmac.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/init.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/padding/zero.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/array.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/bit.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/hex.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/init.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/queue.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/stream.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/default.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/dump.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/eval.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.md delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.md delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/package.json delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/pretty.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.md delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/repl.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/stringify.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/assignment.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/aes.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/issac.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/morus.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/norx.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/blake2b.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md2.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md4.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md5.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha1.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha2.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha3.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/kdf/pbkdf2.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/mac/hmac.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/random.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/eval.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/handlers.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/inbox.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/magic-table.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/print.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/random.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/state.test.js delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.lua delete mode 100644 tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.md diff --git a/.gitignore b/.gitignore index ff666bf..c75755b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ pnpm-debug.log* lerna-debug.log* node_modules -dist dist-ssr dev-dist *.local diff --git a/dist/registry/aos-bundled.lua b/dist/registry/aos-bundled.lua deleted file mode 100644 index d58066d..0000000 --- a/dist/registry/aos-bundled.lua +++ /dev/null @@ -1,327 +0,0 @@ - - --- module: ".vaultString" -local function _loaded_mod_vaultString() -vaultString = [=[ - - - - -local json = require('json') - -SecretVault = SecretVault or {} -SecretVault.State = SecretVault.State or {} -SecretVault.PubSub = SecretVault.PubSub or { Owner, ao.env.Process.Tags['Secretorium-Registry'] } -SecretVault.Controllers = SecretVault.Controllers or { Owner } - -SecretVault.actions = { - set = 'Set', - get = 'Get', - addController = "Add-Controller", - addPubSub = "Add-PubSub" -} - -function SecretVault.authorized(From) - for _, value in ipairs(SecretVault.Controllers) do - if value == From then - return true - end - end - return false -end - --- Set value in nested table based on dot-separated path -function SecretVault.setNestedValue(table, path, value) - local current = table - for segment in string.gmatch(path, "[^%.]+") do - if not current[segment] then - current[segment] = {} - end - current = current[segment] - end - current = value -end - --- Notify all PubSub users of a change -function SecretVault.notifyPubSub(action, path, newController) - local data - if path then - data = "State change at path: " .. path - else - if newController then - data = "New Controller added: " .. newController - end - end - for _, subscriber in ipairs(SecretVault.PubSub) do - local message = { - Target = subscriber, - Action = action .. "-Notification", - Path = path, - ['New-Controller'] = newController, - Data = data - } - Send(message) - end -end - --- Notify owner of unauthorized access attempts -function SecretVault.notifyUnauthorized(action, from) - local message = { - Target = Owner, - Action = "Unauthorized Attempt-Notification", - Data = "Unauthorized attempt to perform action: " .. action .. " by: " .. from - } - Send(message) -end - -Handlers.add( - "Info", - Handlers.utils.hasMatchingTag("Action", "Info"), - function(msg) - Send({ - Target = msg.From, - Action = "Info", - Data = "Owner is: " .. Owner - }) - end -) - -Handlers.add( - "Set", - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.set), - function(msg) - local isAuthorized = SecretVault.authorized(msg.From) - if not isAuthorized then - SecretVault.notifyUnauthorized(SecretVault.actions.set, msg.From) - end - assert(isAuthorized, "unauthorized") - assert(msg.Path, "No Path provided") - assert(msg.Value, "No Value provided") - - SecretVault.setNestedValue(SecretVault.State, msg.Path, msg.Value) - - local message = { - Target = msg.From, - Action = SecretVault.actions.set .. "-Notification", - Path = msg.Path, - Value = msg.Value, - Data = "Value set successfully" - } - Send(message) - SecretVault.notifyPubSub(SecretVault.actions.set, msg.Path) - end -) - -Handlers.add( - "Get", - Handlers.utils.hasMatchingTag('Action', SecretVault.actions.get), - function(msg) - local current = SecretVault.State - if msg.Path then - for segment in string.gmatch(msg.Path, "[^%.]+") do - current = current[segment] - assert(current, "Path not found") - end - end - - local data = json.encode(current) - - local message = { - Target = msg.From, - Action = SecretVault.actions.get .. "-Notification", - Path = msg.Path, - Value = data, - Data = data - } - Send(message) - end -) - -Handlers.add( - "Add-Controller", - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addController), - function(msg) - if msg.From ~= Owner then - SecretVault.notifyUnauthorized(SecretVault.actions.addController, msg.From) - end - assert(msg.From == Owner, "unauthorized") - assert(msg["New-Controller"], "New User not provided") - assert(SecretVault.authorized(msg['New-Controller']) == false, "Already authorized") - table.insert(SecretVault.Controllers, msg["New-Controller"]) - - local message = { - Target = msg.From, - Action = SecretVault.actions.addController .. "-Notification", - ["New-Controller"] = msg["New-Controller"], - Data = "User " .. msg["New-Controller"] .. " added successfully as a controller" - } - Send(message) - Send({ - Target = msg["New-Controller"], - Action = SecretVault.actions.addController .. "-Notification", - Data = "You have been added as an authorized controller for SecretVault" - }) - SecretVault.notifyPubSub(SecretVault.actions.addController, nil, msg["New-Controller"]) - end -) - -Handlers.add( - "Add-PubSub", - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addPubSub), - function(msg) - if msg.From ~= Owner then - SecretVault.notifyUnauthorized(SecretVault.actions.addPubSub, msg.From) - end - assert(msg.From == Owner, "unauthorized") - assert(msg["New-Controller"], "New User not provided") - table.insert(SecretVault.PubSub, msg["New-Controller"]) - - local message = { - Target = msg.From, - Action = SecretVault.actions.addPubSub .. "-Notification", - ["New-Controller"] = msg["New-Controller"], - Data = "User " .. msg["New-Controller"] .. " added successfully to PubSub" - } - Send(message) - Send({ - Target = msg["New-Controller"], - Action = SecretVault.actions.addPubSub .. "-Notification", - Data = "You have been added as a PubSub user for SecretVault" - }) - end -) - - -return SecretVault - - -]=] - -return vaultString -end - -_G.package.loaded[".vaultString"] = _loaded_mod_vaultString() - -local json = require('json') -SecretVault = require('.vaultString') - -local actions = { - spawnVault = "Secretorium.Spawn-Vault", - getVault = "Secretorium.Get-Vault", - getMyVaults = "Secretorium.Get-My-Vaults", - addController = "Add-Controller-Notification" -} - -UserVaultList = UserVaultList or {} -VaultList = VaultList or {} - -local defaultVaultName = "" - -Handlers.add( - "spawn", - Handlers.utils.hasMatchingTag("Action", actions.spawnVault), - function(msg) - - local newVault = Spawn(ao.env.Module.Id, { - Tags = { - ['Secretorium-Registry'] = ao.id, - ['Vault-Name'] = msg['Vault-Name'] or defaultVaultName, - ['Creator'] = msg.From, - ['Id'] = msg['Id'], - ['Authority'] = ao.authorities[1] - } - }).receive({ ['Action'] = 'Spawned' }) - - print("Process = " .. newVault.Process) - print("printing again") - -- print(newVault) - local loadResult = Send({ - Target = newVault.Process, - Action = "Eval", - Data = "Owner = '" .. msg.From .. "' " .. SecretVault - -- Data = SecretVault - }) - - UserVaultList[msg.From] = UserVaultList[msg.From] or {} - UserVaultList[msg.From]["Owner"] = UserVaultList[msg.From]["Owner"] or {} - table.insert(UserVaultList[msg.From]["Owner"], newVault.Process) - - VaultList[newVault.Process] = { - Creator = msg.From, - Controllers = {}, - CreatedAt = msg['Block-Height'], - ['Vault-Name'] = msg['Vault-Name'] or defaultVaultName - } - - Send({ - Target = msg.From, - Action = actions.spawnVault .. "-Notification", - Data = newVault.Process - }) - end -) - - -Handlers.add( - "Add-Controller", - Handlers.utils.hasMatchingTag("Action", actions.addController), - function(msg) - assert(VaultList[msg.From], "Unauthorized") - assert(msg['New-Controller'], "Invalid input, must include new controller") - - UserVaultList[msg['New-Controller']] = UserVaultList[msg['New-Controller']] or {} - UserVaultList[msg['New-Controller']]['Controller'] = UserVaultList[msg['New-Controller']]['Controller'] or {} - table.insert(UserVaultList[msg['New-Controller']]["Controller"], msg.From ) - - table.insert(VaultList[msg.From]['Controllers'], msg['New-Controller']) - end -) - -Handlers.add( - "Get-Vault", - Handlers.utils.hasMatchingTag('Action', actions.getVault), - function(msg) - -- Ensure vault ID is provided and valid - assert(msg['Vault-Id'], "Invalid Input") - assert(VaultList[msg['Vault-Id']], "Vault not found") - - -- Authorization check - local vault = VaultList[msg['Vault-Id']] - local authorized = (vault.Creator == msg.From) - - -- Check if msg.From is in the list of controllers - if not authorized then - for _, controller in ipairs(vault.Controllers or {}) do - if controller == msg.From then - authorized = true - break - end - end - end - - -- Throw unauthorized error if not authorized - assert(authorized, "Unauthorized") - - Send({ - Target = msg.From, - Action = actions.getVault .. "-Notification", - Data = json.encode(vault) - }) - end -) - - -Handlers.add( - 'Get-My-Vaults', - Handlers.utils.hasMatchingTag('Action', actions.getMyVaults), - function(msg) - assert(UserVaultList[msg.From], "No vaults found") - - Send({ - Target = msg.From, - Action = actions.getMyVaults .. "-Notification", - Data = json.encode(UserVaultList[msg.From]) - }) - - end -) \ No newline at end of file diff --git a/dist/vault/aos-bundled.lua b/dist/vault/aos-bundled.lua deleted file mode 100644 index d488989..0000000 --- a/dist/vault/aos-bundled.lua +++ /dev/null @@ -1,188 +0,0 @@ - - -local json = require('json') - -SecretVault = SecretVault or {} -SecretVault.State = SecretVault.State or {} -SecretVault.PubSub = SecretVault.PubSub or { Owner, ao.env.Process.Tags['Secretorium-Registry'] } -SecretVault.Controllers = SecretVault.Controllers or { Owner } - -SecretVault.actions = { - set = 'Set', - get = 'Get', - addController = "Add-Controller", - addPubSub = "Add-PubSub" -} - -function SecretVault.authorized(From) - for _, value in ipairs(SecretVault.Controllers) do - if value == From then - return true - end - end - return false -end - --- Set value in nested table based on dot-separated path -function SecretVault.setNestedValue(table, path, value) - local current = table - for segment in string.gmatch(path, "[^%.]+") do - if not current[segment] then - current[segment] = {} - end - current = current[segment] - end - current = value -end - --- Notify all PubSub users of a change -function SecretVault.notifyPubSub(action, path, newController) - local data - if path then - data = "State change at path: " .. path - else - if newController then - data = "New Controller added: " .. newController - end - end - for _, subscriber in ipairs(SecretVault.PubSub) do - local message = { - Target = subscriber, - Action = action .. "-Notification", - Path = path, - ['New-Controller'] = newController, - Data = data - } - Send(message) - end -end - --- Notify owner of unauthorized access attempts -function SecretVault.notifyUnauthorized(action, from) - local message = { - Target = Owner, - Action = "Unauthorized Attempt-Notification", - Data = "Unauthorized attempt to perform action: " .. action .. " by: " .. from - } - Send(message) -end - -Handlers.add( - "Info", - Handlers.utils.hasMatchingTag("Action", "Info"), - function(msg) - Send({ - Target = msg.From, - Action = "Info", - Data = "Owner is: " .. Owner - }) - end -) - -Handlers.add( - "Set", - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.set), - function(msg) - local isAuthorized = SecretVault.authorized(msg.From) - if not isAuthorized then - SecretVault.notifyUnauthorized(SecretVault.actions.set, msg.From) - end - assert(isAuthorized, "unauthorized") - assert(msg.Path, "No Path provided") - assert(msg.Value, "No Value provided") - - SecretVault.setNestedValue(SecretVault.State, msg.Path, msg.Value) - - local message = { - Target = msg.From, - Action = SecretVault.actions.set .. "-Notification", - Path = msg.Path, - Value = msg.Value, - Data = "Value set successfully" - } - Send(message) - SecretVault.notifyPubSub(SecretVault.actions.set, msg.Path) - end -) - -Handlers.add( - "Get", - Handlers.utils.hasMatchingTag('Action', SecretVault.actions.get), - function(msg) - local current = SecretVault.State - if msg.Path then - for segment in string.gmatch(msg.Path, "[^%.]+") do - current = current[segment] - assert(current, "Path not found") - end - end - - local data = json.encode(current) - - local message = { - Target = msg.From, - Action = SecretVault.actions.get .. "-Notification", - Path = msg.Path, - Value = data, - Data = data - } - Send(message) - end -) - -Handlers.add( - "Add-Controller", - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addController), - function(msg) - if msg.From ~= Owner then - SecretVault.notifyUnauthorized(SecretVault.actions.addController, msg.From) - end - assert(msg.From == Owner, "unauthorized") - assert(msg["New-Controller"], "New User not provided") - assert(SecretVault.authorized(msg['New-Controller']) == false, "Already authorized") - table.insert(SecretVault.Controllers, msg["New-Controller"]) - - local message = { - Target = msg.From, - Action = SecretVault.actions.addController .. "-Notification", - ["New-Controller"] = msg["New-Controller"], - Data = "User " .. msg["New-Controller"] .. " added successfully as a controller" - } - Send(message) - Send({ - Target = msg["New-Controller"], - Action = SecretVault.actions.addController .. "-Notification", - Data = "You have been added as an authorized controller for SecretVault" - }) - SecretVault.notifyPubSub(SecretVault.actions.addController, nil, msg["New-Controller"]) - end -) - -Handlers.add( - "Add-PubSub", - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addPubSub), - function(msg) - if msg.From ~= Owner then - SecretVault.notifyUnauthorized(SecretVault.actions.addPubSub, msg.From) - end - assert(msg.From == Owner, "unauthorized") - assert(msg["New-Controller"], "New User not provided") - table.insert(SecretVault.PubSub, msg["New-Controller"]) - - local message = { - Target = msg.From, - Action = SecretVault.actions.addPubSub .. "-Notification", - ["New-Controller"] = msg["New-Controller"], - Data = "User " .. msg["New-Controller"] .. " added successfully to PubSub" - } - Send(message) - Send({ - Target = msg["New-Controller"], - Action = SecretVault.actions.addPubSub .. "-Notification", - Data = "You have been added as a PubSub user for SecretVault" - }) - end -) - - -return SecretVault diff --git a/package.json b/package.json index 713af80..614cc44 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@babel/preset-typescript": "^7.18.6", "@commitlint/cli": "^17.6.7", "@commitlint/config-conventional": "^17.6.7", + "@permaweb/ao-loader": "^0.0.43", "@testing-library/jest-dom": "^6.1.5", "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.1", diff --git a/tools/bundle-aos.js b/tools/bundle-aos.js index 3f3888d..1f41a77 100644 --- a/tools/bundle-aos.js +++ b/tools/bundle-aos.js @@ -1,10 +1,8 @@ import * as fs from 'fs'; import * as path from 'path'; -import { fileURLToPath } from 'url'; import { bundle } from './lua-bundler.js'; -const __dirname = path.dirname(fileURLToPath(import.meta.url)); async function main() { const args = process.argv.slice(2); // Get CLI arguments const pathArg = args.find((arg) => arg.startsWith('--path=')); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/.gitignore b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/.gitignore deleted file mode 100644 index 704a0a2..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# lock files -package-lock.json -yarn.lock - -# binaries -process.js -process.wasm \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/README.md b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/README.md deleted file mode 100644 index ca4ab5c..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# aos - -This is the source code to the aos module, this module provides developers with the capability of designing and building process on the ao network in an interactive experience. When the design is complete the developer can transfer the ownership to a DAO process or brick the ownership so that the process can never be modified. - -## Build - -```sh -yarn build -``` - -## Testing - -```sh -yarn test -``` - -## Modules - -- [process](process.md) -- [handlers](handlers.md) -- [ao](ao.md) diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/ao.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/ao.lua deleted file mode 100644 index faa6105..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/ao.lua +++ /dev/null @@ -1,296 +0,0 @@ -local oldao = ao or {} -local ao = { - _version = "0.0.6", - id = oldao.id or "", - _module = oldao._module or "", - authorities = oldao.authorities or {}, - reference = oldao.reference or 0, - outbox = oldao.outbox or - {Output = {}, Messages = {}, Spawns = {}, Assignments = {}}, - nonExtractableTags = { - 'Data-Protocol', 'Variant', 'From-Process', 'From-Module', 'Type', - 'From', 'Owner', 'Anchor', 'Target', 'Data', 'Tags' - }, - nonForwardableTags = { - 'Data-Protocol', 'Variant', 'From-Process', 'From-Module', 'Type', - 'From', 'Owner', 'Anchor', 'Target', 'Tags', 'TagArray', 'Hash-Chain', - 'Timestamp', 'Nonce', 'Epoch', 'Signature', 'Forwarded-By', - 'Pushed-For', 'Read-Only', 'Cron', 'Block-Height', 'Reference', 'Id', - 'Reply-To' - } -} - -local function _includes(list) - return function(key) - local exists = false - for _, listKey in ipairs(list) do - if key == listKey then - exists = true - break - end - end - if not exists then return false end - return true - end -end - -local function isArray(table) - if type(table) == "table" then - local maxIndex = 0 - for k, v in pairs(table) do - if type(k) ~= "number" or k < 1 or math.floor(k) ~= k then - return false -- If there's a non-integer key, it's not an array - end - maxIndex = math.max(maxIndex, k) - end - -- If the highest numeric index is equal to the number of elements, it's an array - return maxIndex == #table - end - return false -end - -local function padZero32(num) return string.format("%032d", num) end - -function ao.clone(obj, seen) - -- Handle non-tables and previously-seen tables. - if type(obj) ~= 'table' then return obj end - if seen and seen[obj] then return seen[obj] end - - -- New table; mark it as seen and copy recursively. - local s = seen or {} - local res = {} - s[obj] = res - for k, v in pairs(obj) do res[ao.clone(k, s)] = ao.clone(v, s) end - return setmetatable(res, getmetatable(obj)) -end - -function ao.normalize(msg) - for _, o in ipairs(msg.Tags) do - if not _includes(ao.nonExtractableTags)(o.name) then - msg[o.name] = o.value - end - end - return msg -end - -function ao.sanitize(msg) - local newMsg = ao.clone(msg) - - for k, _ in pairs(newMsg) do - if _includes(ao.nonForwardableTags)(k) then newMsg[k] = nil end - end - - return newMsg -end - -function ao.init(env) - if ao.id == "" then ao.id = env.Process.Id end - - if ao._module == "" then - for _, o in ipairs(env.Process.Tags) do - if o.name == "Module" then ao._module = o.value end - end - end - - if #ao.authorities < 1 then - for _, o in ipairs(env.Process.Tags) do - if o.name == "Authority" then - table.insert(ao.authorities, o.value) - end - end - end - - ao.outbox = {Output = {}, Messages = {}, Spawns = {}, Assignments = {}} - ao.env = env - -end - -function ao.log(txt) - if type(ao.outbox.Output) == 'string' then - ao.outbox.Output = {ao.outbox.Output} - end - table.insert(ao.outbox.Output, txt) -end - --- clears outbox -function ao.clearOutbox() - ao.outbox = {Output = {}, Messages = {}, Spawns = {}, Assignments = {}} -end - -function ao.send(msg) - assert(type(msg) == 'table', 'msg should be a table') - ao.reference = ao.reference + 1 - local referenceString = tostring(ao.reference) - - local message = { - Target = msg.Target, - Data = msg.Data, - Anchor = padZero32(ao.reference), - Tags = { - {name = "Data-Protocol", value = "ao"}, - {name = "Variant", value = "ao.TN.1"}, - {name = "Type", value = "Message"}, - {name = "Reference", value = referenceString} - } - } - - -- if custom tags in root move them to tags - for k, v in pairs(msg) do - if not _includes({"Target", "Data", "Anchor", "Tags", "From"})(k) then - table.insert(message.Tags, {name = k, value = v}) - end - end - - if msg.Tags then - if isArray(msg.Tags) then - for _, o in ipairs(msg.Tags) do - table.insert(message.Tags, o) - end - else - for k, v in pairs(msg.Tags) do - table.insert(message.Tags, {name = k, value = v}) - end - end - end - - -- If running in an environment without the AOS Handlers module, do not add - -- the onReply and receive functions to the message. - if not Handlers then return message end - - -- clone message info and add to outbox - local extMessage = {} - for k, v in pairs(message) do extMessage[k] = v end - - -- add message to outbox - table.insert(ao.outbox.Messages, extMessage) - - -- add callback for onReply handler(s) - message.onReply = - function(...) -- Takes either (AddressThatWillReply, handler(s)) or (handler(s)) - local from, resolver - if select("#", ...) == 2 then - from = select(1, ...) - resolver = select(2, ...) - else - from = message.Target - resolver = select(1, ...) - end - - -- Add a one-time callback that runs the user's (matching) resolver on reply - Handlers.once({From = from, ["X-Reference"] = referenceString}, - resolver) - end - - message.receive = function(...) - local from = message.Target - if select("#", ...) == 1 then from = select(1, ...) end - return - Handlers.receive({From = from, ["X-Reference"] = referenceString}) - end - - return message -end - -function ao.spawn(module, msg) - assert(type(module) == "string", "Module source id is required!") - assert(type(msg) == 'table', 'Message must be a table') - -- inc spawn reference - ao.reference = ao.reference + 1 - local spawnRef = tostring(ao.reference) - - local spawn = { - Data = msg.Data or "NODATA", - Anchor = padZero32(ao.reference), - Tags = { - {name = "Data-Protocol", value = "ao"}, - {name = "Variant", value = "ao.TN.1"}, - {name = "Type", value = "Process"}, - {name = "From-Process", value = ao.id}, - {name = "From-Module", value = ao._module}, - {name = "Module", value = module}, - {name = "Reference", value = spawnRef} - } - } - - -- if custom tags in root move them to tags - for k, v in pairs(msg) do - if not _includes({"Target", "Data", "Anchor", "Tags", "From"})(k) then - table.insert(spawn.Tags, {name = k, value = v}) - end - end - - if msg.Tags then - if isArray(msg.Tags) then - for _, o in ipairs(msg.Tags) do - table.insert(spawn.Tags, o) - end - else - for k, v in pairs(msg.Tags) do - table.insert(spawn.Tags, {name = k, value = v}) - end - end - end - - -- If running in an environment without the AOS Handlers module, do not add - -- the after and receive functions to the spawn. - if not Handlers then return spawn end - - -- clone spawn info and add to outbox - local extSpawn = {} - for k, v in pairs(spawn) do extSpawn[k] = v end - - table.insert(ao.outbox.Spawns, extSpawn) - - -- add 'after' callback to returned table - -- local result = {} - spawn.onReply = function(callback) - Handlers.once({ - Action = "Spawned", - From = ao.id, - ["Reference"] = spawnRef - }, callback) - end - - spawn.receive = function() - return Handlers.receive({ - Action = "Spawned", - From = ao.id, - ["Reference"] = spawnRef - }) - - end - - return spawn -end - -function ao.assign(assignment) - assert(type(assignment) == 'table', 'assignment should be a table') - assert(type(assignment.Processes) == 'table', 'Processes should be a table') - assert(type(assignment.Message) == "string", "Message should be a string") - table.insert(ao.outbox.Assignments, assignment) -end - --- The default security model of AOS processes: Trust all and *only* those --- on the ao.authorities list. -function ao.isTrusted(msg) - for _, authority in ipairs(ao.authorities) do - if msg.From == authority then return true end - if msg.Owner == authority then return true end - end - return false -end - -function ao.result(result) - -- if error then only send the Error to CU - if ao.outbox.Error or result.Error then - return {Error = result.Error or ao.outbox.Error} - end - return { - Output = result.Output or ao.outbox.Output, - Messages = ao.outbox.Messages, - Spawns = ao.outbox.Spawns, - Assignments = ao.outbox.Assignments - } -end - -return ao diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/assignment.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/assignment.lua deleted file mode 100644 index 5ba30f0..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/assignment.lua +++ /dev/null @@ -1,98 +0,0 @@ - -local Assignment = { _version = "0.1.0" } -local utils = require('.utils') - --- Implement assignable polyfills on _ao -function Assignment.init (ao) - local function findIndexByProp(array, prop, value) - for index, object in ipairs(array) do - if object[prop] == value then return index end - end - - return nil - end - - ao.assignables = ao.assignables or {} - - -- Add the MatchSpec to the ao.assignables table. A optional name may be provided. - -- This implies that ao.assignables may have both number and string indices. - -- - -- @tparam ?string|number|any nameOrMatchSpec The name of the MatchSpec - -- to be added to ao.assignables. if a MatchSpec is provided, then - -- no name is included - -- @tparam ?any matchSpec The MatchSpec to be added to ao.assignables. Only provided - -- if its name is passed as the first parameter - -- @treturn ?string|number name The name of the MatchSpec, either as provided - -- as an argument or as incremented - ao.addAssignable = ao.addAssignable or function (...) - local name = nil - local matchSpec = nil - - local idx = nil - - -- Initialize the parameters based on arguments - if select("#", ...) == 1 then - matchSpec = select(1, ...) - else - name = select(1, ...) - matchSpec = select(2, ...) - assert(type(name) == 'string', 'MatchSpec name MUST be a string') - end - - if name then idx = findIndexByProp(ao.assignables, "name", name) end - - if idx ~= nil and idx > 0 then - -- found update - ao.assignables[idx].pattern = matchSpec - else - -- append the new assignable, including potentially nil name - table.insert(ao.assignables, { pattern = matchSpec, name = name }) - end - end - - -- Remove the MatchSpec, either by name or by index - -- If the name is not found, or if the index does not exist, then do nothing. - -- - -- @tparam string|number name The name or index of the MatchSpec to be removed - -- @treturn nil nil - ao.removeAssignable = ao.removeAssignable or function (name) - local idx = nil - - if type(name) == 'string' then idx = findIndexByProp(ao.assignables, "name", name) - else - assert(type(name) == 'number', 'index MUST be a number') - idx = name - end - - if idx == nil or idx <= 0 or idx > #ao.assignables then return end - - table.remove(ao.assignables, idx) - end - - -- Return whether the msg is an assignment or not. This - -- can be determined by simply check whether the msg's Target is - -- This process' id - -- - -- @param msg The msg to be checked - -- @treturn boolean isAssignment - ao.isAssignment = ao.isAssignment or function (msg) return msg.Target ~= ao.id end - - -- Check whether the msg matches any assignable MatchSpec. - -- If not assignables are configured, the msg is deemed not assignable, by default. - -- - -- @param msg The msg to be checked - -- @treturn boolean isAssignable - ao.isAssignable = ao.isAssignable or function (msg) - for _, assignable in pairs(ao.assignables) do - if utils.matchesSpec(msg, assignable.pattern) then return true end - end - - -- If assignables is empty, the the above loop will noop, - -- and this expression will execute. - -- - -- In other words, all msgs are not assignable, by default. - return false - end -end - -return Assignment diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/base64.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/base64.lua deleted file mode 100644 index 6bf473f..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/base64.lua +++ /dev/null @@ -1,201 +0,0 @@ ---[[ - - base64 -- v1.5.3 public domain Lua base64 encoder/decoder - no warranty implied; use at your own risk - - Needs bit32.extract function. If not present it's implemented using BitOp - or Lua 5.3 native bit operators. For Lua 5.1 fallbacks to pure Lua - implementation inspired by Rici Lake's post: - http://ricilake.blogspot.co.uk/2007/10/iterating-bits-in-lua.html - - author: Ilya Kolbin (iskolbin@gmail.com) - url: github.com/iskolbin/lbase64 - - COMPATIBILITY - - Lua 5.1+, LuaJIT - - LICENSE - - See end of file for license information. - ---]] - - -local base64 = {} - -local extract = _G.bit32 and _G.bit32.extract -- Lua 5.2/Lua 5.3 in compatibility mode -if not extract then - if _G.bit then -- LuaJIT - local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band - extract = function( v, from, width ) - return band( shr( v, from ), shl( 1, width ) - 1 ) - end - elseif _G._VERSION == "Lua 5.1" then - extract = function( v, from, width ) - local w = 0 - local flag = 2^from - for i = 0, width-1 do - local flag2 = flag + flag - if v % flag2 >= flag then - w = w + 2^i - end - flag = flag2 - end - return w - end - else -- Lua 5.3+ - extract = load[[return function( v, from, width ) - return ( v >> from ) & ((1 << width) - 1) - end]]() - end -end - - -function base64.makeencoder( s62, s63, spad ) - local encoder = {} - for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J', - 'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y', - 'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n', - 'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2', - '3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do - encoder[b64code] = char:byte() - end - return encoder -end - -function base64.makedecoder( s62, s63, spad ) - local decoder = {} - for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do - decoder[charcode] = b64code - end - return decoder -end - -local DEFAULT_ENCODER = base64.makeencoder() -local DEFAULT_DECODER = base64.makedecoder() - -local char, concat = string.char, table.concat - -function base64.encode( str, encoder, usecaching ) - encoder = encoder or DEFAULT_ENCODER - local t, k, n = {}, 1, #str - local lastn = n % 3 - local cache = {} - for i = 1, n-lastn, 3 do - local a, b, c = str:byte( i, i+2 ) - local v = a*0x10000 + b*0x100 + c - local s - if usecaching then - s = cache[v] - if not s then - s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)]) - cache[v] = s - end - else - s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)]) - end - t[k] = s - k = k + 1 - end - if lastn == 2 then - local a, b = str:byte( n-1, n ) - local v = a*0x10000 + b*0x100 - t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64]) - elseif lastn == 1 then - local v = str:byte( n )*0x10000 - t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64]) - end - return concat( t ) -end - -function base64.decode( b64, decoder, usecaching ) - decoder = decoder or DEFAULT_DECODER - local pattern = '[^%w%+%/%=]' - if decoder then - local s62, s63 - for charcode, b64code in pairs( decoder ) do - if b64code == 62 then s62 = charcode - elseif b64code == 63 then s63 = charcode - end - end - pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) ) - end - b64 = b64:gsub( pattern, '' ) - local cache = usecaching and {} - local t, k = {}, 1 - local n = #b64 - local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0 - for i = 1, padding > 0 and n-4 or n, 4 do - local a, b, c, d = b64:byte( i, i+3 ) - local s - if usecaching then - local v0 = a*0x1000000 + b*0x10000 + c*0x100 + d - s = cache[v0] - if not s then - local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d] - s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8)) - cache[v0] = s - end - else - local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d] - s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8)) - end - t[k] = s - k = k + 1 - end - if padding == 1 then - local a, b, c = b64:byte( n-3, n-1 ) - local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 - t[k] = char( extract(v,16,8), extract(v,8,8)) - elseif padding == 2 then - local a, b = b64:byte( n-3, n-2 ) - local v = decoder[a]*0x40000 + decoder[b]*0x1000 - t[k] = char( extract(v,16,8)) - end - return concat( t ) -end - -return base64 - ---[[ ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2018 Ilya Kolbin -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- ---]] \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/bint.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/bint.lua deleted file mode 100644 index ce2164a..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/bint.lua +++ /dev/null @@ -1,1739 +0,0 @@ ---[[-- -lua-bint - v0.5.1 - 26/Jun/2023 -Eduardo Bart - edub4rt@gmail.com -https://github.com/edubart/lua-bint - -Small portable arbitrary-precision integer arithmetic library in pure Lua for -computing with large integers. - -Different from most arbitrary-precision integer libraries in pure Lua out there this one -uses an array of lua integers as underlying data-type in its implementation instead of -using strings or large tables, this make it efficient for working with fixed width integers -and to make bitwise operations. - -## Design goals - -The main design goal of this library is to be small, correct, self contained and use few -resources while retaining acceptable performance and feature completeness. - -The library is designed to follow recent Lua integer semantics, this means that -integer overflow warps around, -signed integers are implemented using two-complement arithmetic rules, -integer division operations rounds towards minus infinity, -any mixed operations with float numbers promotes the value to a float, -and the usual division/power operation always promotes to floats. - -The library is designed to be possible to work with only unsigned integer arithmetic -when using the proper methods. - -All the lua arithmetic operators (+, -, *, //, /, %) and bitwise operators (&, |, ~, <<, >>) -are implemented as metamethods. - -The integer size must be fixed in advance and the library is designed to be more efficient when -working with integers of sizes between 64-4096 bits. If you need to work with really huge numbers -without size restrictions then use another library. This choice has been made to have more efficiency -in that specific size range. - -## Usage - -First on you should require the bint file including how many bits the bint module will work with, -by calling the returned function from the require, for example: - -```lua -local bint = require 'bint'(1024) -``` - -For more information about its arguments see @{newmodule}. -Then when you need create a bint, you can use one of the following functions: - -* @{bint.fromuinteger} (convert from lua integers, but read as unsigned integer) -* @{bint.frominteger} (convert from lua integers, preserving the sign) -* @{bint.frombase} (convert from arbitrary bases, like hexadecimal) -* @{bint.fromstring} (convert from arbitrary string, support binary/hexadecimal/decimal) -* @{bint.trunc} (convert from lua numbers, truncating the fractional part) -* @{bint.new} (convert from anything, asserts on invalid integers) -* @{bint.tobint} (convert from anything, returns nil on invalid integers) -* @{bint.parse} (convert from anything, returns a lua number as fallback) -* @{bint.zero} -* @{bint.one} -* `bint` - -You can also call `bint` as it is an alias to `bint.new`. -In doubt use @{bint.new} to create a new bint. - -Then you can use all the usual lua numeric operations on it, -all the arithmetic metamethods are implemented. -When you are done computing and need to get the result, -get the output from one of the following functions: - -* @{bint.touinteger} (convert to a lua integer, wraps around as an unsigned integer) -* @{bint.tointeger} (convert to a lua integer, wraps around, preserves the sign) -* @{bint.tonumber} (convert to lua float, losing precision) -* @{bint.tobase} (convert to a string in any base) -* @{bint.__tostring} (convert to a string in base 10) - -To output a very large integer with no loss you probably want to use @{bint.tobase} -or call `tostring` to get a string representation. - -## Precautions - -All library functions can be mixed with lua numbers, -this makes easy to mix operations between bints and lua numbers, -however the user should take care in some situations: - -* Don't mix integers and float operations if you want to work with integers only. -* Don't use the regular equal operator ('==') to compare values from this library, -unless you know in advance that both values are of the same primitive type, -otherwise it will always return false, use @{bint.eq} to be safe. -* Don't pass fractional numbers to functions that an integer is expected -* Don't mix operations between bint classes with different sizes as this is not supported, this -will throw assertions. -* Remember that casting back to lua integers or numbers precision can be lost. -* For dividing while preserving integers use the @{bint.__idiv} (the '//' operator). -* For doing power operation preserving integers use the @{bint.ipow} function. -* Configure the proper integer size you intend to work with, otherwise large integers may wrap around. - -]] - --- Returns number of bits of the internal lua integer type. -local function luainteger_bitsize() - local n, i = -1, 0 - repeat - n, i = n >> 16, i + 16 - until n==0 - return i -end - -local math_type = math.type -local math_floor = math.floor -local math_abs = math.abs -local math_ceil = math.ceil -local math_modf = math.modf -local math_mininteger = math.mininteger -local math_maxinteger = math.maxinteger -local math_max = math.max -local math_min = math.min -local string_format = string.format -local table_insert = table.insert -local table_concat = table.concat -local table_unpack = table.unpack - -local memo = {} - ---- Create a new bint module representing integers of the desired bit size. --- This is the returned function when `require 'bint'` is called. --- @function newmodule --- @param bits Number of bits for the integer representation, must be multiple of wordbits and --- at least 64. --- @param[opt] wordbits Number of the bits for the internal word, --- defaults to half of Lua's integer size. -local function newmodule(bits, wordbits) - -local intbits = luainteger_bitsize() -bits = bits or 256 -wordbits = wordbits or (intbits // 2) - --- Memoize bint modules -local memoindex = bits * 64 + wordbits -if memo[memoindex] then - return memo[memoindex] -end - --- Validate -assert(bits % wordbits == 0, 'bitsize is not multiple of word bitsize') -assert(2*wordbits <= intbits, 'word bitsize must be half of the lua integer bitsize') -assert(bits >= 64, 'bitsize must be >= 64') -assert(wordbits >= 8, 'wordbits must be at least 8') -assert(bits % 8 == 0, 'bitsize must be multiple of 8') - --- Create bint module -local bint = {} -bint.__index = bint - ---- Number of bits representing a bint instance. -bint.bits = bits - --- Constants used internally -local BINT_BITS = bits -local BINT_BYTES = bits // 8 -local BINT_WORDBITS = wordbits -local BINT_SIZE = BINT_BITS // BINT_WORDBITS -local BINT_WORDMAX = (1 << BINT_WORDBITS) - 1 -local BINT_WORDMSB = (1 << (BINT_WORDBITS - 1)) -local BINT_LEPACKFMT = '<'..('I'..(wordbits // 8)):rep(BINT_SIZE) -local BINT_MATHMININTEGER, BINT_MATHMAXINTEGER -local BINT_MININTEGER - ---- Create a new bint with 0 value. -function bint.zero() - local x = setmetatable({}, bint) - for i=1,BINT_SIZE do - x[i] = 0 - end - return x -end -local bint_zero = bint.zero - ---- Create a new bint with 1 value. -function bint.one() - local x = setmetatable({}, bint) - x[1] = 1 - for i=2,BINT_SIZE do - x[i] = 0 - end - return x -end -local bint_one = bint.one - --- Convert a value to a lua integer without losing precision. -local function tointeger(x) - x = tonumber(x) - local ty = math_type(x) - if ty == 'float' then - local floorx = math_floor(x) - if floorx == x then - x = floorx - ty = math_type(x) - end - end - if ty == 'integer' then - return x - end -end - ---- Create a bint from an unsigned integer. --- Treats signed integers as an unsigned integer. --- @param x A value to initialize from convertible to a lua integer. --- @return A new bint or nil in case the input cannot be represented by an integer. --- @see bint.frominteger -function bint.fromuinteger(x) - x = tointeger(x) - if x then - if x == 1 then - return bint_one() - elseif x == 0 then - return bint_zero() - end - local n = setmetatable({}, bint) - for i=1,BINT_SIZE do - n[i] = x & BINT_WORDMAX - x = x >> BINT_WORDBITS - end - return n - end -end -local bint_fromuinteger = bint.fromuinteger - ---- Create a bint from a signed integer. --- @param x A value to initialize from convertible to a lua integer. --- @return A new bint or nil in case the input cannot be represented by an integer. --- @see bint.fromuinteger -function bint.frominteger(x) - x = tointeger(x) - if x then - if x == 1 then - return bint_one() - elseif x == 0 then - return bint_zero() - end - local neg = false - if x < 0 then - x = math_abs(x) - neg = true - end - local n = setmetatable({}, bint) - for i=1,BINT_SIZE do - n[i] = x & BINT_WORDMAX - x = x >> BINT_WORDBITS - end - if neg then - n:_unm() - end - return n - end -end -local bint_frominteger = bint.frominteger - -local basesteps = {} - --- Compute the read step for frombase function -local function getbasestep(base) - local step = basesteps[base] - if step then - return step - end - step = 0 - local dmax = 1 - local limit = math_maxinteger // base - repeat - step = step + 1 - dmax = dmax * base - until dmax >= limit - basesteps[base] = step - return step -end - --- Compute power with lua integers. -local function ipow(y, x, n) - if n == 1 then - return y * x - elseif n & 1 == 0 then --even - return ipow(y, x * x, n // 2) - end - return ipow(x * y, x * x, (n-1) // 2) -end - ---- Create a bint from a string of the desired base. --- @param s The string to be converted from, --- must have only alphanumeric and '+-' characters. --- @param[opt] base Base that the number is represented, defaults to 10. --- Must be at least 2 and at most 36. --- @return A new bint or nil in case the conversion failed. -function bint.frombase(s, base) - if type(s) ~= 'string' then - return - end - base = base or 10 - if not (base >= 2 and base <= 36) then - -- number base is too large - return - end - local step = getbasestep(base) - if #s < step then - -- string is small, use tonumber (faster) - return bint_frominteger(tonumber(s, base)) - end - local sign, int = s:lower():match('^([+-]?)(%w+)$') - if not (sign and int) then - -- invalid integer string representation - return - end - local n = bint_zero() - for i=1,#int,step do - local part = int:sub(i,i+step-1) - local d = tonumber(part, base) - if not d then - -- invalid integer string representation - return - end - if i > 1 then - n = n * ipow(1, base, #part) - end - if d ~= 0 then - n:_add(d) - end - end - if sign == '-' then - n:_unm() - end - return n -end -local bint_frombase = bint.frombase - ---- Create a new bint from a string. --- The string can by a decimal number, binary number prefixed with '0b' or hexadecimal number prefixed with '0x'. --- @param s A string convertible to a bint. --- @return A new bint or nil in case the conversion failed. --- @see bint.frombase -function bint.fromstring(s) - if type(s) ~= 'string' then - return - end - if s:find('^[+-]?[0-9]+$') then - return bint_frombase(s, 10) - elseif s:find('^[+-]?0[xX][0-9a-fA-F]+$') then - return bint_frombase(s:gsub('0[xX]', '', 1), 16) - elseif s:find('^[+-]?0[bB][01]+$') then - return bint_frombase(s:gsub('0[bB]', '', 1), 2) - end -end -local bint_fromstring = bint.fromstring - ---- Create a new bint from a buffer of little-endian bytes. --- @param buffer Buffer of bytes, extra bytes are trimmed from the right, missing bytes are padded to the right. --- @raise An assert is thrown in case buffer is not an string. --- @return A bint. -function bint.fromle(buffer) - assert(type(buffer) == 'string', 'buffer is not a string') - if #buffer > BINT_BYTES then -- trim extra bytes from the right - buffer = buffer:sub(1, BINT_BYTES) - elseif #buffer < BINT_BYTES then -- add missing bytes to the right - buffer = buffer..('\x00'):rep(BINT_BYTES - #buffer) - end - return setmetatable({BINT_LEPACKFMT:unpack(buffer)}, bint) -end - ---- Create a new bint from a buffer of big-endian bytes. --- @param buffer Buffer of bytes, extra bytes are trimmed from the left, missing bytes are padded to the left. --- @raise An assert is thrown in case buffer is not an string. --- @return A bint. -function bint.frombe(buffer) - assert(type(buffer) == 'string', 'buffer is not a string') - if #buffer > BINT_BYTES then -- trim extra bytes from the left - buffer = buffer:sub(-BINT_BYTES, #buffer) - elseif #buffer < BINT_BYTES then -- add missing bytes to the left - buffer = ('\x00'):rep(BINT_BYTES - #buffer)..buffer - end - return setmetatable({BINT_LEPACKFMT:unpack(buffer:reverse())}, bint) -end - ---- Create a new bint from a value. --- @param x A value convertible to a bint (string, number or another bint). --- @return A new bint, guaranteed to be a new reference in case needed. --- @raise An assert is thrown in case x is not convertible to a bint. --- @see bint.tobint --- @see bint.parse -function bint.new(x) - if getmetatable(x) ~= bint then - local ty = type(x) - if ty == 'number' then - x = bint_frominteger(x) - elseif ty == 'string' then - x = bint_fromstring(x) - end - assert(x, 'value cannot be represented by a bint') - return x - end - -- return a clone - local n = setmetatable({}, bint) - for i=1,BINT_SIZE do - n[i] = x[i] - end - return n -end -local bint_new = bint.new - ---- Convert a value to a bint if possible. --- @param x A value to be converted (string, number or another bint). --- @param[opt] clone A boolean that tells if a new bint reference should be returned. --- Defaults to false. --- @return A bint or nil in case the conversion failed. --- @see bint.new --- @see bint.parse -function bint.tobint(x, clone) - if getmetatable(x) == bint then - if not clone then - return x - end - -- return a clone - local n = setmetatable({}, bint) - for i=1,BINT_SIZE do - n[i] = x[i] - end - return n - end - local ty = type(x) - if ty == 'number' then - return bint_frominteger(x) - elseif ty == 'string' then - return bint_fromstring(x) - end -end -local tobint = bint.tobint - ---- Convert a value to a bint if possible otherwise to a lua number. --- Useful to prepare values that you are unsure if it's going to be an integer or float. --- @param x A value to be converted (string, number or another bint). --- @param[opt] clone A boolean that tells if a new bint reference should be returned. --- Defaults to false. --- @return A bint or a lua number or nil in case the conversion failed. --- @see bint.new --- @see bint.tobint -function bint.parse(x, clone) - local i = tobint(x, clone) - if i then - return i - end - return tonumber(x) -end -local bint_parse = bint.parse - ---- Convert a bint to an unsigned integer. --- Note that large unsigned integers may be represented as negatives in lua integers. --- Note that lua cannot represent values larger than 64 bits, --- in that case integer values wrap around. --- @param x A bint or a number to be converted into an unsigned integer. --- @return An integer or nil in case the input cannot be represented by an integer. --- @see bint.tointeger -function bint.touinteger(x) - if getmetatable(x) == bint then - local n = 0 - for i=1,BINT_SIZE do - n = n | (x[i] << (BINT_WORDBITS * (i - 1))) - end - return n - end - return tointeger(x) -end - ---- Convert a bint to a signed integer. --- It works by taking absolute values then applying the sign bit in case needed. --- Note that lua cannot represent values larger than 64 bits, --- in that case integer values wrap around. --- @param x A bint or value to be converted into an unsigned integer. --- @return An integer or nil in case the input cannot be represented by an integer. --- @see bint.touinteger -function bint.tointeger(x) - if getmetatable(x) == bint then - local n = 0 - local neg = x:isneg() - if neg then - x = -x - end - for i=1,BINT_SIZE do - n = n | (x[i] << (BINT_WORDBITS * (i - 1))) - end - if neg then - n = -n - end - return n - end - return tointeger(x) -end -local bint_tointeger = bint.tointeger - -local function bint_assert_tointeger(x) - x = bint_tointeger(x) - if not x then - error('value has no integer representation') - end - return x -end - ---- Convert a bint to a lua float in case integer would wrap around or lua integer otherwise. --- Different from @{bint.tointeger} the operation does not wrap around integers, --- but digits precision are lost in the process of converting to a float. --- @param x A bint or value to be converted into a lua number. --- @return A lua number or nil in case the input cannot be represented by a number. --- @see bint.tointeger -function bint.tonumber(x) - if getmetatable(x) == bint then - if x <= BINT_MATHMAXINTEGER and x >= BINT_MATHMININTEGER then - return x:tointeger() - end - return tonumber(tostring(x)) - end - return tonumber(x) -end -local bint_tonumber = bint.tonumber - --- Compute base letters to use in bint.tobase -local BASE_LETTERS = {} -do - for i=1,36 do - BASE_LETTERS[i-1] = ('0123456789abcdefghijklmnopqrstuvwxyz'):sub(i,i) - end -end - ---- Convert a bint to a string in the desired base. --- @param x The bint to be converted from. --- @param[opt] base Base to be represented, defaults to 10. --- Must be at least 2 and at most 36. --- @param[opt] unsigned Whether to output as an unsigned integer. --- Defaults to false for base 10 and true for others. --- When unsigned is false the symbol '-' is prepended in negative values. --- @return A string representing the input. --- @raise An assert is thrown in case the base is invalid. -function bint.tobase(x, base, unsigned) - x = tobint(x) - if not x then - -- x is a fractional float or something else - return - end - base = base or 10 - if not (base >= 2 and base <= 36) then - -- number base is too large - return - end - if unsigned == nil then - unsigned = base ~= 10 - end - local isxneg = x:isneg() - if (base == 10 and not unsigned) or (base == 16 and unsigned and not isxneg) then - if x <= BINT_MATHMAXINTEGER and x >= BINT_MATHMININTEGER then - -- integer is small, use tostring or string.format (faster) - local n = x:tointeger() - if base == 10 then - return tostring(n) - elseif unsigned then - return string_format('%x', n) - end - end - end - local ss = {} - local neg = not unsigned and isxneg - x = neg and x:abs() or bint_new(x) - local xiszero = x:iszero() - if xiszero then - return '0' - end - -- calculate basepow - local step = 0 - local basepow = 1 - local limit = (BINT_WORDMSB - 1) // base - repeat - step = step + 1 - basepow = basepow * base - until basepow >= limit - -- serialize base digits - local size = BINT_SIZE - local xd, carry, d - repeat - -- single word division - carry = 0 - xiszero = true - for i=size,1,-1 do - carry = carry | x[i] - d, xd = carry // basepow, carry % basepow - if xiszero and d ~= 0 then - size = i - xiszero = false - end - x[i] = d - carry = xd << BINT_WORDBITS - end - -- digit division - for _=1,step do - xd, d = xd // base, xd % base - if xiszero and xd == 0 and d == 0 then - -- stop on leading zeros - break - end - table_insert(ss, 1, BASE_LETTERS[d]) - end - until xiszero - if neg then - table_insert(ss, 1, '-') - end - return table_concat(ss) -end - -local function bint_assert_convert(x) - return assert(tobint(x), 'value has not integer representation') -end - ---- Convert a bint to a buffer of little-endian bytes. --- @param x A bint or lua integer. --- @param[opt] trim If true, zero bytes on the right are trimmed. --- @return A buffer of bytes representing the input. --- @raise Asserts in case input is not convertible to an integer. -function bint.tole(x, trim) - x = bint_assert_convert(x) - local s = BINT_LEPACKFMT:pack(table_unpack(x)) - if trim then - s = s:gsub('\x00+$', '') - if s == '' then - s = '\x00' - end - end - return s -end - ---- Convert a bint to a buffer of big-endian bytes. --- @param x A bint or lua integer. --- @param[opt] trim If true, zero bytes on the left are trimmed. --- @return A buffer of bytes representing the input. --- @raise Asserts in case input is not convertible to an integer. -function bint.tobe(x, trim) - x = bint_assert_convert(x) - local s = BINT_LEPACKFMT:pack(table_unpack(x)):reverse() - if trim then - s = s:gsub('^\x00+', '') - if s == '' then - s = '\x00' - end - end - return s -end - ---- Check if a number is 0 considering bints. --- @param x A bint or a lua number. -function bint.iszero(x) - if getmetatable(x) == bint then - for i=1,BINT_SIZE do - if x[i] ~= 0 then - return false - end - end - return true - end - return x == 0 -end - ---- Check if a number is 1 considering bints. --- @param x A bint or a lua number. -function bint.isone(x) - if getmetatable(x) == bint then - if x[1] ~= 1 then - return false - end - for i=2,BINT_SIZE do - if x[i] ~= 0 then - return false - end - end - return true - end - return x == 1 -end - ---- Check if a number is -1 considering bints. --- @param x A bint or a lua number. -function bint.isminusone(x) - if getmetatable(x) == bint then - for i=1,BINT_SIZE do - if x[i] ~= BINT_WORDMAX then - return false - end - end - return true - end - return x == -1 -end -local bint_isminusone = bint.isminusone - ---- Check if the input is a bint. --- @param x Any lua value. -function bint.isbint(x) - return getmetatable(x) == bint -end - ---- Check if the input is a lua integer or a bint. --- @param x Any lua value. -function bint.isintegral(x) - return getmetatable(x) == bint or math_type(x) == 'integer' -end - ---- Check if the input is a bint or a lua number. --- @param x Any lua value. -function bint.isnumeric(x) - return getmetatable(x) == bint or type(x) == 'number' -end - ---- Get the number type of the input (bint, integer or float). --- @param x Any lua value. --- @return Returns "bint" for bints, "integer" for lua integers, --- "float" from lua floats or nil otherwise. -function bint.type(x) - if getmetatable(x) == bint then - return 'bint' - end - return math_type(x) -end - ---- Check if a number is negative considering bints. --- Zero is guaranteed to never be negative for bints. --- @param x A bint or a lua number. -function bint.isneg(x) - if getmetatable(x) == bint then - return x[BINT_SIZE] & BINT_WORDMSB ~= 0 - end - return x < 0 -end -local bint_isneg = bint.isneg - ---- Check if a number is positive considering bints. --- @param x A bint or a lua number. -function bint.ispos(x) - if getmetatable(x) == bint then - return not x:isneg() and not x:iszero() - end - return x > 0 -end - ---- Check if a number is even considering bints. --- @param x A bint or a lua number. -function bint.iseven(x) - if getmetatable(x) == bint then - return x[1] & 1 == 0 - end - return math_abs(x) % 2 == 0 -end - ---- Check if a number is odd considering bints. --- @param x A bint or a lua number. -function bint.isodd(x) - if getmetatable(x) == bint then - return x[1] & 1 == 1 - end - return math_abs(x) % 2 == 1 -end - ---- Create a new bint with the maximum possible integer value. -function bint.maxinteger() - local x = setmetatable({}, bint) - for i=1,BINT_SIZE-1 do - x[i] = BINT_WORDMAX - end - x[BINT_SIZE] = BINT_WORDMAX ~ BINT_WORDMSB - return x -end - ---- Create a new bint with the minimum possible integer value. -function bint.mininteger() - local x = setmetatable({}, bint) - for i=1,BINT_SIZE-1 do - x[i] = 0 - end - x[BINT_SIZE] = BINT_WORDMSB - return x -end - ---- Bitwise left shift a bint in one bit (in-place). -function bint:_shlone() - local wordbitsm1 = BINT_WORDBITS - 1 - for i=BINT_SIZE,2,-1 do - self[i] = ((self[i] << 1) | (self[i-1] >> wordbitsm1)) & BINT_WORDMAX - end - self[1] = (self[1] << 1) & BINT_WORDMAX - return self -end - ---- Bitwise right shift a bint in one bit (in-place). -function bint:_shrone() - local wordbitsm1 = BINT_WORDBITS - 1 - for i=1,BINT_SIZE-1 do - self[i] = ((self[i] >> 1) | (self[i+1] << wordbitsm1)) & BINT_WORDMAX - end - self[BINT_SIZE] = self[BINT_SIZE] >> 1 - return self -end - --- Bitwise left shift words of a bint (in-place). Used only internally. -function bint:_shlwords(n) - for i=BINT_SIZE,n+1,-1 do - self[i] = self[i - n] - end - for i=1,n do - self[i] = 0 - end - return self -end - --- Bitwise right shift words of a bint (in-place). Used only internally. -function bint:_shrwords(n) - if n < BINT_SIZE then - for i=1,BINT_SIZE-n do - self[i] = self[i + n] - end - for i=BINT_SIZE-n+1,BINT_SIZE do - self[i] = 0 - end - else - for i=1,BINT_SIZE do - self[i] = 0 - end - end - return self -end - ---- Increment a bint by one (in-place). -function bint:_inc() - for i=1,BINT_SIZE do - local tmp = self[i] - local v = (tmp + 1) & BINT_WORDMAX - self[i] = v - if v > tmp then - break - end - end - return self -end - ---- Increment a number by one considering bints. --- @param x A bint or a lua number to increment. -function bint.inc(x) - local ix = tobint(x, true) - if ix then - return ix:_inc() - end - return x + 1 -end - ---- Decrement a bint by one (in-place). -function bint:_dec() - for i=1,BINT_SIZE do - local tmp = self[i] - local v = (tmp - 1) & BINT_WORDMAX - self[i] = v - if v <= tmp then - break - end - end - return self -end - ---- Decrement a number by one considering bints. --- @param x A bint or a lua number to decrement. -function bint.dec(x) - local ix = tobint(x, true) - if ix then - return ix:_dec() - end - return x - 1 -end - ---- Assign a bint to a new value (in-place). --- @param y A value to be copied from. --- @raise Asserts in case inputs are not convertible to integers. -function bint:_assign(y) - y = bint_assert_convert(y) - for i=1,BINT_SIZE do - self[i] = y[i] - end - return self -end - ---- Take absolute of a bint (in-place). -function bint:_abs() - if self:isneg() then - self:_unm() - end - return self -end - ---- Take absolute of a number considering bints. --- @param x A bint or a lua number to take the absolute. -function bint.abs(x) - local ix = tobint(x, true) - if ix then - return ix:_abs() - end - return math_abs(x) -end -local bint_abs = bint.abs - ---- Take the floor of a number considering bints. --- @param x A bint or a lua number to perform the floor operation. -function bint.floor(x) - if getmetatable(x) == bint then - return bint_new(x) - end - return bint_new(math_floor(tonumber(x))) -end - ---- Take ceil of a number considering bints. --- @param x A bint or a lua number to perform the ceil operation. -function bint.ceil(x) - if getmetatable(x) == bint then - return bint_new(x) - end - return bint_new(math_ceil(tonumber(x))) -end - ---- Wrap around bits of an integer (discarding left bits) considering bints. --- @param x A bint or a lua integer. --- @param y Number of right bits to preserve. -function bint.bwrap(x, y) - x = bint_assert_convert(x) - if y <= 0 then - return bint_zero() - elseif y < BINT_BITS then - return x & (bint_one() << y):_dec() - end - return bint_new(x) -end - ---- Rotate left integer x by y bits considering bints. --- @param x A bint or a lua integer. --- @param y Number of bits to rotate. -function bint.brol(x, y) - x, y = bint_assert_convert(x), bint_assert_tointeger(y) - if y > 0 then - return (x << y) | (x >> (BINT_BITS - y)) - elseif y < 0 then - if y ~= math_mininteger then - return x:bror(-y) - else - x:bror(-(y+1)) - x:bror(1) - end - end - return x -end - ---- Rotate right integer x by y bits considering bints. --- @param x A bint or a lua integer. --- @param y Number of bits to rotate. -function bint.bror(x, y) - x, y = bint_assert_convert(x), bint_assert_tointeger(y) - if y > 0 then - return (x >> y) | (x << (BINT_BITS - y)) - elseif y < 0 then - if y ~= math_mininteger then - return x:brol(-y) - else - x:brol(-(y+1)) - x:brol(1) - end - end - return x -end - ---- Truncate a number to a bint. --- Floats numbers are truncated, that is, the fractional port is discarded. --- @param x A number to truncate. --- @return A new bint or nil in case the input does not fit in a bint or is not a number. -function bint.trunc(x) - if getmetatable(x) ~= bint then - x = tonumber(x) - if x then - local ty = math_type(x) - if ty == 'float' then - -- truncate to integer - x = math_modf(x) - end - return bint_frominteger(x) - end - return - end - return bint_new(x) -end - ---- Take maximum between two numbers considering bints. --- @param x A bint or lua number to compare. --- @param y A bint or lua number to compare. --- @return A bint or a lua number. Guarantees to return a new bint for integer values. -function bint.max(x, y) - local ix, iy = tobint(x), tobint(y) - if ix and iy then - return bint_new(ix > iy and ix or iy) - end - return bint_parse(math_max(x, y)) -end - ---- Take minimum between two numbers considering bints. --- @param x A bint or lua number to compare. --- @param y A bint or lua number to compare. --- @return A bint or a lua number. Guarantees to return a new bint for integer values. -function bint.min(x, y) - local ix, iy = tobint(x), tobint(y) - if ix and iy then - return bint_new(ix < iy and ix or iy) - end - return bint_parse(math_min(x, y)) -end - ---- Add an integer to a bint (in-place). --- @param y An integer to be added. --- @raise Asserts in case inputs are not convertible to integers. -function bint:_add(y) - y = bint_assert_convert(y) - local carry = 0 - for i=1,BINT_SIZE do - local tmp = self[i] + y[i] + carry - carry = tmp >> BINT_WORDBITS - self[i] = tmp & BINT_WORDMAX - end - return self -end - ---- Add two numbers considering bints. --- @param x A bint or a lua number to be added. --- @param y A bint or a lua number to be added. -function bint.__add(x, y) - local ix, iy = tobint(x), tobint(y) - if ix and iy then - local z = setmetatable({}, bint) - local carry = 0 - for i=1,BINT_SIZE do - local tmp = ix[i] + iy[i] + carry - carry = tmp >> BINT_WORDBITS - z[i] = tmp & BINT_WORDMAX - end - return z - end - return bint_tonumber(x) + bint_tonumber(y) -end - ---- Subtract an integer from a bint (in-place). --- @param y An integer to subtract. --- @raise Asserts in case inputs are not convertible to integers. -function bint:_sub(y) - y = bint_assert_convert(y) - local borrow = 0 - local wordmaxp1 = BINT_WORDMAX + 1 - for i=1,BINT_SIZE do - local res = self[i] + wordmaxp1 - y[i] - borrow - self[i] = res & BINT_WORDMAX - borrow = (res >> BINT_WORDBITS) ~ 1 - end - return self -end - ---- Subtract two numbers considering bints. --- @param x A bint or a lua number to be subtracted from. --- @param y A bint or a lua number to subtract. -function bint.__sub(x, y) - local ix, iy = tobint(x), tobint(y) - if ix and iy then - local z = setmetatable({}, bint) - local borrow = 0 - local wordmaxp1 = BINT_WORDMAX + 1 - for i=1,BINT_SIZE do - local res = ix[i] + wordmaxp1 - iy[i] - borrow - z[i] = res & BINT_WORDMAX - borrow = (res >> BINT_WORDBITS) ~ 1 - end - return z - end - return bint_tonumber(x) - bint_tonumber(y) -end - ---- Multiply two numbers considering bints. --- @param x A bint or a lua number to multiply. --- @param y A bint or a lua number to multiply. -function bint.__mul(x, y) - local ix, iy = tobint(x), tobint(y) - if ix and iy then - local z = bint_zero() - local sizep1 = BINT_SIZE+1 - local s = sizep1 - local e = 0 - for i=1,BINT_SIZE do - if ix[i] ~= 0 or iy[i] ~= 0 then - e = math_max(e, i) - s = math_min(s, i) - end - end - for i=s,e do - for j=s,math_min(sizep1-i,e) do - local a = ix[i] * iy[j] - if a ~= 0 then - local carry = 0 - for k=i+j-1,BINT_SIZE do - local tmp = z[k] + (a & BINT_WORDMAX) + carry - carry = tmp >> BINT_WORDBITS - z[k] = tmp & BINT_WORDMAX - a = a >> BINT_WORDBITS - end - end - end - end - return z - end - return bint_tonumber(x) * bint_tonumber(y) -end - ---- Check if bints are equal. --- @param x A bint to compare. --- @param y A bint to compare. -function bint.__eq(x, y) - for i=1,BINT_SIZE do - if x[i] ~= y[i] then - return false - end - end - return true -end - ---- Check if numbers are equal considering bints. --- @param x A bint or lua number to compare. --- @param y A bint or lua number to compare. -function bint.eq(x, y) - local ix, iy = tobint(x), tobint(y) - if ix and iy then - return ix == iy - end - return x == y -end -local bint_eq = bint.eq - -local function findleftbit(x) - for i=BINT_SIZE,1,-1 do - local v = x[i] - if v ~= 0 then - local j = 0 - repeat - v = v >> 1 - j = j + 1 - until v == 0 - return (i-1)*BINT_WORDBITS + j - 1, i - end - end -end - --- Single word division modulus -local function sudivmod(nume, deno) - local rema - local carry = 0 - for i=BINT_SIZE,1,-1 do - carry = carry | nume[i] - nume[i] = carry // deno - rema = carry % deno - carry = rema << BINT_WORDBITS - end - return rema -end - ---- Perform unsigned division and modulo operation between two integers considering bints. --- This is effectively the same of @{bint.udiv} and @{bint.umod}. --- @param x The numerator, must be a bint or a lua integer. --- @param y The denominator, must be a bint or a lua integer. --- @return The quotient following the remainder, both bints. --- @raise Asserts on attempt to divide by zero --- or if inputs are not convertible to integers. --- @see bint.udiv --- @see bint.umod -function bint.udivmod(x, y) - local nume = bint_new(x) - local deno = bint_assert_convert(y) - -- compute if high bits of denominator are all zeros - local ishighzero = true - for i=2,BINT_SIZE do - if deno[i] ~= 0 then - ishighzero = false - break - end - end - if ishighzero then - -- try to divide by a single word (optimization) - local low = deno[1] - assert(low ~= 0, 'attempt to divide by zero') - if low == 1 then - -- denominator is one - return nume, bint_zero() - elseif low <= (BINT_WORDMSB - 1) then - -- can do single word division - local rema = sudivmod(nume, low) - return nume, bint_fromuinteger(rema) - end - end - if nume:ult(deno) then - -- denominator is greater than numerator - return bint_zero(), nume - end - -- align leftmost digits in numerator and denominator - local denolbit = findleftbit(deno) - local numelbit, numesize = findleftbit(nume) - local bit = numelbit - denolbit - deno = deno << bit - local wordmaxp1 = BINT_WORDMAX + 1 - local wordbitsm1 = BINT_WORDBITS - 1 - local denosize = numesize - local quot = bint_zero() - while bit >= 0 do - -- compute denominator <= numerator - local le = true - local size = math_max(numesize, denosize) - for i=size,1,-1 do - local a, b = deno[i], nume[i] - if a ~= b then - le = a < b - break - end - end - -- if the portion of the numerator above the denominator is greater or equal than to the denominator - if le then - -- subtract denominator from the portion of the numerator - local borrow = 0 - for i=1,size do - local res = nume[i] + wordmaxp1 - deno[i] - borrow - nume[i] = res & BINT_WORDMAX - borrow = (res >> BINT_WORDBITS) ~ 1 - end - -- concatenate 1 to the right bit of the quotient - local i = (bit // BINT_WORDBITS) + 1 - quot[i] = quot[i] | (1 << (bit % BINT_WORDBITS)) - end - -- shift right the denominator in one bit - for i=1,denosize-1 do - deno[i] = ((deno[i] >> 1) | (deno[i+1] << wordbitsm1)) & BINT_WORDMAX - end - local lastdenoword = deno[denosize] >> 1 - deno[denosize] = lastdenoword - -- recalculate denominator size (optimization) - if lastdenoword == 0 then - while deno[denosize] == 0 do - denosize = denosize - 1 - end - if denosize == 0 then - break - end - end - -- decrement current set bit for the quotient - bit = bit - 1 - end - -- the remaining numerator is the remainder - return quot, nume -end -local bint_udivmod = bint.udivmod - ---- Perform unsigned division between two integers considering bints. --- @param x The numerator, must be a bint or a lua integer. --- @param y The denominator, must be a bint or a lua integer. --- @return The quotient, a bint. --- @raise Asserts on attempt to divide by zero --- or if inputs are not convertible to integers. -function bint.udiv(x, y) - return (bint_udivmod(x, y)) -end - ---- Perform unsigned integer modulo operation between two integers considering bints. --- @param x The numerator, must be a bint or a lua integer. --- @param y The denominator, must be a bint or a lua integer. --- @return The remainder, a bint. --- @raise Asserts on attempt to divide by zero --- or if the inputs are not convertible to integers. -function bint.umod(x, y) - local _, rema = bint_udivmod(x, y) - return rema -end -local bint_umod = bint.umod - ---- Perform integer truncate division and modulo operation between two numbers considering bints. --- This is effectively the same of @{bint.tdiv} and @{bint.tmod}. --- @param x The numerator, a bint or lua number. --- @param y The denominator, a bint or lua number. --- @return The quotient following the remainder, both bint or lua number. --- @raise Asserts on attempt to divide by zero or on division overflow. --- @see bint.tdiv --- @see bint.tmod -function bint.tdivmod(x, y) - local ax, ay = bint_abs(x), bint_abs(y) - local ix, iy = tobint(ax), tobint(ay) - local quot, rema - if ix and iy then - assert(not (bint_eq(x, BINT_MININTEGER) and bint_isminusone(y)), 'division overflow') - quot, rema = bint_udivmod(ix, iy) - else - quot, rema = ax // ay, ax % ay - end - local isxneg, isyneg = bint_isneg(x), bint_isneg(y) - if isxneg ~= isyneg then - quot = -quot - end - if isxneg then - rema = -rema - end - return quot, rema -end -local bint_tdivmod = bint.tdivmod - ---- Perform truncate division between two numbers considering bints. --- Truncate division is a division that rounds the quotient towards zero. --- @param x The numerator, a bint or lua number. --- @param y The denominator, a bint or lua number. --- @return The quotient, a bint or lua number. --- @raise Asserts on attempt to divide by zero or on division overflow. -function bint.tdiv(x, y) - return (bint_tdivmod(x, y)) -end - ---- Perform integer truncate modulo operation between two numbers considering bints. --- The operation is defined as the remainder of the truncate division --- (division that rounds the quotient towards zero). --- @param x The numerator, a bint or lua number. --- @param y The denominator, a bint or lua number. --- @return The remainder, a bint or lua number. --- @raise Asserts on attempt to divide by zero or on division overflow. -function bint.tmod(x, y) - local _, rema = bint_tdivmod(x, y) - return rema -end - ---- Perform integer floor division and modulo operation between two numbers considering bints. --- This is effectively the same of @{bint.__idiv} and @{bint.__mod}. --- @param x The numerator, a bint or lua number. --- @param y The denominator, a bint or lua number. --- @return The quotient following the remainder, both bint or lua number. --- @raise Asserts on attempt to divide by zero. --- @see bint.__idiv --- @see bint.__mod -function bint.idivmod(x, y) - local ix, iy = tobint(x), tobint(y) - if ix and iy then - local isnumeneg = ix[BINT_SIZE] & BINT_WORDMSB ~= 0 - local isdenoneg = iy[BINT_SIZE] & BINT_WORDMSB ~= 0 - if isnumeneg then - ix = -ix - end - if isdenoneg then - iy = -iy - end - local quot, rema = bint_udivmod(ix, iy) - if isnumeneg ~= isdenoneg then - quot:_unm() - -- round quotient towards minus infinity - if not rema:iszero() then - quot:_dec() - -- adjust the remainder - if isnumeneg and not isdenoneg then - rema:_unm():_add(y) - elseif isdenoneg and not isnumeneg then - rema:_add(y) - end - end - elseif isnumeneg then - -- adjust the remainder - rema:_unm() - end - return quot, rema - end - local nx, ny = bint_tonumber(x), bint_tonumber(y) - return nx // ny, nx % ny -end -local bint_idivmod = bint.idivmod - ---- Perform floor division between two numbers considering bints. --- Floor division is a division that rounds the quotient towards minus infinity, --- resulting in the floor of the division of its operands. --- @param x The numerator, a bint or lua number. --- @param y The denominator, a bint or lua number. --- @return The quotient, a bint or lua number. --- @raise Asserts on attempt to divide by zero. -function bint.__idiv(x, y) - local ix, iy = tobint(x), tobint(y) - if ix and iy then - local isnumeneg = ix[BINT_SIZE] & BINT_WORDMSB ~= 0 - local isdenoneg = iy[BINT_SIZE] & BINT_WORDMSB ~= 0 - if isnumeneg then - ix = -ix - end - if isdenoneg then - iy = -iy - end - local quot, rema = bint_udivmod(ix, iy) - if isnumeneg ~= isdenoneg then - quot:_unm() - -- round quotient towards minus infinity - if not rema:iszero() then - quot:_dec() - end - end - return quot, rema - end - return bint_tonumber(x) // bint_tonumber(y) -end - ---- Perform division between two numbers considering bints. --- This always casts inputs to floats, for integer division only use @{bint.__idiv}. --- @param x The numerator, a bint or lua number. --- @param y The denominator, a bint or lua number. --- @return The quotient, a lua number. -function bint.__div(x, y) - return bint_tonumber(x) / bint_tonumber(y) -end - ---- Perform integer floor modulo operation between two numbers considering bints. --- The operation is defined as the remainder of the floor division --- (division that rounds the quotient towards minus infinity). --- @param x The numerator, a bint or lua number. --- @param y The denominator, a bint or lua number. --- @return The remainder, a bint or lua number. --- @raise Asserts on attempt to divide by zero. -function bint.__mod(x, y) - local _, rema = bint_idivmod(x, y) - return rema -end - ---- Perform integer power between two integers considering bints. --- If y is negative then pow is performed as an unsigned integer. --- @param x The base, an integer. --- @param y The exponent, an integer. --- @return The result of the pow operation, a bint. --- @raise Asserts in case inputs are not convertible to integers. --- @see bint.__pow --- @see bint.upowmod -function bint.ipow(x, y) - y = bint_assert_convert(y) - if y:iszero() then - return bint_one() - elseif y:isone() then - return bint_new(x) - end - -- compute exponentiation by squaring - x, y = bint_new(x), bint_new(y) - local z = bint_one() - repeat - if y:iseven() then - x = x * x - y:_shrone() - else - z = x * z - x = x * x - y:_dec():_shrone() - end - until y:isone() - return x * z -end - ---- Perform integer power between two unsigned integers over a modulus considering bints. --- @param x The base, an integer. --- @param y The exponent, an integer. --- @param m The modulus, an integer. --- @return The result of the pow operation, a bint. --- @raise Asserts in case inputs are not convertible to integers. --- @see bint.__pow --- @see bint.ipow -function bint.upowmod(x, y, m) - m = bint_assert_convert(m) - if m:isone() then - return bint_zero() - end - x, y = bint_new(x), bint_new(y) - local z = bint_one() - x = bint_umod(x, m) - while not y:iszero() do - if y:isodd() then - z = bint_umod(z*x, m) - end - y:_shrone() - x = bint_umod(x*x, m) - end - return z -end - ---- Perform numeric power between two numbers considering bints. --- This always casts inputs to floats, for integer power only use @{bint.ipow}. --- @param x The base, a bint or lua number. --- @param y The exponent, a bint or lua number. --- @return The result of the pow operation, a lua number. --- @see bint.ipow -function bint.__pow(x, y) - return bint_tonumber(x) ^ bint_tonumber(y) -end - ---- Bitwise left shift integers considering bints. --- @param x An integer to perform the bitwise shift. --- @param y An integer with the number of bits to shift. --- @return The result of shift operation, a bint. --- @raise Asserts in case inputs are not convertible to integers. -function bint.__shl(x, y) - x, y = bint_new(x), bint_assert_tointeger(y) - if y == math_mininteger or math_abs(y) >= BINT_BITS then - return bint_zero() - end - if y < 0 then - return x >> -y - end - local nvals = y // BINT_WORDBITS - if nvals ~= 0 then - x:_shlwords(nvals) - y = y - nvals * BINT_WORDBITS - end - if y ~= 0 then - local wordbitsmy = BINT_WORDBITS - y - for i=BINT_SIZE,2,-1 do - x[i] = ((x[i] << y) | (x[i-1] >> wordbitsmy)) & BINT_WORDMAX - end - x[1] = (x[1] << y) & BINT_WORDMAX - end - return x -end - ---- Bitwise right shift integers considering bints. --- @param x An integer to perform the bitwise shift. --- @param y An integer with the number of bits to shift. --- @return The result of shift operation, a bint. --- @raise Asserts in case inputs are not convertible to integers. -function bint.__shr(x, y) - x, y = bint_new(x), bint_assert_tointeger(y) - if y == math_mininteger or math_abs(y) >= BINT_BITS then - return bint_zero() - end - if y < 0 then - return x << -y - end - local nvals = y // BINT_WORDBITS - if nvals ~= 0 then - x:_shrwords(nvals) - y = y - nvals * BINT_WORDBITS - end - if y ~= 0 then - local wordbitsmy = BINT_WORDBITS - y - for i=1,BINT_SIZE-1 do - x[i] = ((x[i] >> y) | (x[i+1] << wordbitsmy)) & BINT_WORDMAX - end - x[BINT_SIZE] = x[BINT_SIZE] >> y - end - return x -end - ---- Bitwise AND bints (in-place). --- @param y An integer to perform bitwise AND. --- @raise Asserts in case inputs are not convertible to integers. -function bint:_band(y) - y = bint_assert_convert(y) - for i=1,BINT_SIZE do - self[i] = self[i] & y[i] - end - return self -end - ---- Bitwise AND two integers considering bints. --- @param x An integer to perform bitwise AND. --- @param y An integer to perform bitwise AND. --- @raise Asserts in case inputs are not convertible to integers. -function bint.__band(x, y) - return bint_new(x):_band(y) -end - ---- Bitwise OR bints (in-place). --- @param y An integer to perform bitwise OR. --- @raise Asserts in case inputs are not convertible to integers. -function bint:_bor(y) - y = bint_assert_convert(y) - for i=1,BINT_SIZE do - self[i] = self[i] | y[i] - end - return self -end - ---- Bitwise OR two integers considering bints. --- @param x An integer to perform bitwise OR. --- @param y An integer to perform bitwise OR. --- @raise Asserts in case inputs are not convertible to integers. -function bint.__bor(x, y) - return bint_new(x):_bor(y) -end - ---- Bitwise XOR bints (in-place). --- @param y An integer to perform bitwise XOR. --- @raise Asserts in case inputs are not convertible to integers. -function bint:_bxor(y) - y = bint_assert_convert(y) - for i=1,BINT_SIZE do - self[i] = self[i] ~ y[i] - end - return self -end - ---- Bitwise XOR two integers considering bints. --- @param x An integer to perform bitwise XOR. --- @param y An integer to perform bitwise XOR. --- @raise Asserts in case inputs are not convertible to integers. -function bint.__bxor(x, y) - return bint_new(x):_bxor(y) -end - ---- Bitwise NOT a bint (in-place). -function bint:_bnot() - for i=1,BINT_SIZE do - self[i] = (~self[i]) & BINT_WORDMAX - end - return self -end - ---- Bitwise NOT a bint. --- @param x An integer to perform bitwise NOT. --- @raise Asserts in case inputs are not convertible to integers. -function bint.__bnot(x) - local y = setmetatable({}, bint) - for i=1,BINT_SIZE do - y[i] = (~x[i]) & BINT_WORDMAX - end - return y -end - ---- Negate a bint (in-place). This effectively applies two's complements. -function bint:_unm() - return self:_bnot():_inc() -end - ---- Negate a bint. This effectively applies two's complements. --- @param x A bint to perform negation. -function bint.__unm(x) - return (~x):_inc() -end - ---- Compare if integer x is less than y considering bints (unsigned version). --- @param x Left integer to compare. --- @param y Right integer to compare. --- @raise Asserts in case inputs are not convertible to integers. --- @see bint.__lt -function bint.ult(x, y) - x, y = bint_assert_convert(x), bint_assert_convert(y) - for i=BINT_SIZE,1,-1 do - local a, b = x[i], y[i] - if a ~= b then - return a < b - end - end - return false -end - ---- Compare if bint x is less or equal than y considering bints (unsigned version). --- @param x Left integer to compare. --- @param y Right integer to compare. --- @raise Asserts in case inputs are not convertible to integers. --- @see bint.__le -function bint.ule(x, y) - x, y = bint_assert_convert(x), bint_assert_convert(y) - for i=BINT_SIZE,1,-1 do - local a, b = x[i], y[i] - if a ~= b then - return a < b - end - end - return true -end - ---- Compare if number x is less than y considering bints and signs. --- @param x Left value to compare, a bint or lua number. --- @param y Right value to compare, a bint or lua number. --- @see bint.ult -function bint.__lt(x, y) - local ix, iy = tobint(x), tobint(y) - if ix and iy then - local xneg = ix[BINT_SIZE] & BINT_WORDMSB ~= 0 - local yneg = iy[BINT_SIZE] & BINT_WORDMSB ~= 0 - if xneg == yneg then - for i=BINT_SIZE,1,-1 do - local a, b = ix[i], iy[i] - if a ~= b then - return a < b - end - end - return false - end - return xneg and not yneg - end - return bint_tonumber(x) < bint_tonumber(y) -end - ---- Compare if number x is less or equal than y considering bints and signs. --- @param x Left value to compare, a bint or lua number. --- @param y Right value to compare, a bint or lua number. --- @see bint.ule -function bint.__le(x, y) - local ix, iy = tobint(x), tobint(y) - if ix and iy then - local xneg = ix[BINT_SIZE] & BINT_WORDMSB ~= 0 - local yneg = iy[BINT_SIZE] & BINT_WORDMSB ~= 0 - if xneg == yneg then - for i=BINT_SIZE,1,-1 do - local a, b = ix[i], iy[i] - if a ~= b then - return a < b - end - end - return true - end - return xneg and not yneg - end - return bint_tonumber(x) <= bint_tonumber(y) -end - ---- Convert a bint to a string on base 10. --- @see bint.tobase -function bint:__tostring() - return self:tobase(10) -end - --- Allow creating bints by calling bint itself -setmetatable(bint, { - __call = function(_, x) - return bint_new(x) - end -}) - -BINT_MATHMININTEGER, BINT_MATHMAXINTEGER = bint_new(math.mininteger), bint_new(math.maxinteger) -BINT_MININTEGER = bint.mininteger() -memo[memoindex] = bint - -return bint - -end - -return newmodule \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/chance.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/chance.lua deleted file mode 100644 index c6f576a..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/chance.lua +++ /dev/null @@ -1,86 +0,0 @@ -local N = 624 -local M = 397 -local MATRIX_A = 0x9908b0df -local UPPER_MASK = 0x80000000 -local LOWER_MASK = 0x7fffffff - --- initializes mt[N] with a seed -local function init_genrand(o, s) - o.mt[0] = s & 0xffffffff - for i = 1, N - 1 do - o.mt[i] = (1812433253 * (o.mt[i - 1] ~ (o.mt[i - 1] >> 30))) + i - -- See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. - -- In the previous versions, MSBs of the seed affect - -- only MSBs of the array mt[]. - -- 2002/01/09 modified by Makoto Matsumoto - o.mt[i] = o.mt[i] & 0xffffffff - -- for >32 bit machines - end - o.mti = N -end - --- generates a random number on [0,0xffffffff]-interval -local function genrand_int32(o) - local y - local mag01 = {} -- mag01[x] = x * MATRIX_A for x=0,1 - mag01[0] = 0x0 - mag01[1] = MATRIX_A - if o.mti >= N then -- generate N words at one time - if o.mti == N + 1 then -- if init_genrand() has not been called, - init_genrand(o, 5489) -- a default initial seed is used - end - for kk = 0, N - M - 1 do - y = (o.mt[kk] & UPPER_MASK) | (o.mt[kk + 1] & LOWER_MASK) - o.mt[kk] = o.mt[kk + M] ~ (y >> 1) ~ mag01[y & 0x1] - end - for kk = N - M, N - 2 do - y = (o.mt[kk] & UPPER_MASK) | (o.mt[kk + 1] & LOWER_MASK) - o.mt[kk] = o.mt[kk + (M - N)] ~ (y >> 1) ~ mag01[y & 0x1] - end - y = (o.mt[N - 1] & UPPER_MASK) | (o.mt[0] & LOWER_MASK) - o.mt[N - 1] = o.mt[M - 1] ~ (y >> 1) ~ mag01[y & 0x1] - - o.mti = 0 - end - - y = o.mt[o.mti] - o.mti = o.mti + 1 - - -- Tempering - y = y ~ (y >> 11) - y = y ~ ((y << 7) & 0x9d2c5680) - y = y ~ ((y << 15) & 0xefc60000) - y = y ~ (y >> 18) - - return y -end - -local MersenneTwister = {} -MersenneTwister.mt = {} -MersenneTwister.mti = N + 1 - - -local Random = {} - --- set new random seed -function Random.seed(seed) - init_genrand(MersenneTwister, seed) -end - --- generates a random number on [0,1)-real-interval -function Random.random() - return genrand_int32(MersenneTwister) * (1.0 / 4294967296.0) -end - ---[[ -return a random integer -NOTE the min and max are INCLUDED in the range. -the max integer in lua is math.maxinteger -the min is math.mininteger -]] -function Random.integer(min, max) - assert(max >= min, "max must bigger than min") - return math.floor(Random.random() * (max - min + 1) + min) -end - -return Random \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto.md b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto.md deleted file mode 100644 index aa38864..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto.md +++ /dev/null @@ -1,852 +0,0 @@ -# README for Lua Module: `crypto` (v0.0.1) - -## Overview - -The `crypto` module provides a set of cryptographic primitives like digests, ciphers and other cryptographic algorithms in pure Lua. It offers several functionalities to hash, encrypt and decrypt data, simplifying the development of secure communication and data storage. This document will guide you through the module's functionalities, installation, and usage. - -### Version - -0.0.1 - -## Installation - -1. Ensure you have Lua installed on your AOS computer system. -2. Copy the `crypto` folder to your project directory or a designated Lua libraries directory. -3. Include the module in your Lua scripts using `local crypto = require('.crypto')`. - -## Primitives - -1. Digests (sha1, sha2, sha3, keccak, blake2b, etc.) -2. Ciphers (AES, ISSAC, Morus, NORX, etc.) -3. Random Number Generators (ISAAC) -4. MACs (HMAC) -5. KDFs (PBKDF2) -6. Utilities (Array, Stream, Queue, etc.) - ---- - -# Digests - -## MD2 - -Calculates the MD2 digest of a given message. - -- **Parameters:** - - - `stream` (Stream): The message in form of stream - -- **Returns:** A table containing functions to get digest in different formats. - - `asBytes()`: The digest as byte table. - - `asHex()`: The digest as string in hexadecimal format. - - `asString()`: The digest as string format. - -Example: - -```lua -local str = crypto.utils.stream.fromString("ao") - -return crypto.digest.md2(str).asHex() -- 0d4e80edd07bee6c7965b21b25a9b1ea -``` - -## MD4 - -Calculates the MD4 digest of a given message. - -- **Parameters:** - - - `stream` (Stream): The message in form of stream - -- **Returns:** A table containing functions to get digest in different formats. - - `asBytes()`: The digest as byte table. - - `asHex()`: The digest as string in hexadecimal format. - - `asString()`: The digest as string format. - -Example: - -```lua -local str = crypto.utils.stream.fromString("ao") - -return crypto.digest.md4(str).asHex() -- e068dfe3d8cb95311b58be566db66954 -``` - -## MD5 - -Calculates the MD5 digest of a given message. - -- **Parameters:** - - - `stream` (Stream): The message in form of stream - -- **Returns:** A table containing functions to get digest in different formats. - - `asBytes()`: The digest as byte table. - - `asHex()`: The digest as string in hexadecimal format. - - `asString()`: The digest as string format. - -Example: - -```lua -local str = crypto.utils.stream.fromString("ao") - -return crypto.digest.md5(str).asHex() -- adac5e63f80f8629e9573527b25891d3 -``` - -## SHA1 - -Calculates the SHA1 digest of a given message. - -- **Parameters:** - - - `stream` (Stream): The message in form of stream - -- **Returns:** A table containing functions to get digest in different formats. - - `asBytes()`: The digest as byte table. - - `asHex()`: The digest as string in hexadecimal format. - - `asString()`: The digest as string format. - -Example: - -```lua -local str = crypto.utils.stream.fromString("ao") - -return crypto.digest.sha1(str).asHex() -- c29dd6c83b67a1d6d3b28588a1f068b68689aa1d -``` - -## SHA2_256 - -Calculates the SHA2-256 digest of a given message. - -- **Parameters:** - - `stream` (Stream): The message in form of stream -- **Returns:** A table containing functions to get digest in different formats. - - `asBytes()`: The digest as byte table. - - `asHex()`: The digest as string in hexadecimal format. - - `asString()`: The digest as string format. - -Example: - -```lua -local str = crypto.utils.stream.fromString("ao") - -return crypto.digest.sha2_256(str).asHex() -- ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad -``` - -## SHA2_512 - -Calculates the SHA2-512 digest of a given message. - -- **Parameters:** - - `msg` (string): The message to calculate the digest -- **Returns:** A table containing functions to get digest in different formats. - - `asBytes()`: The digest as byte table. - - `asHex()`: The digest as string in hexadecimal format. - - `asString()`: The digest as string format. - -Example: - -```lua -local str = "ao" - -return crypto.digest.sha2_512(str).asHex() -- 6f36a696b17ce5a71efa700e8a7e47994f3e134a5e5f387b3e7c2c912abe94f94ee823f9b9dcae59af99e2e34c8b4fb0bd592260c6720ee49e5deaac2065c4b1 -``` - -## SHA3 - -It contains the following functions: - -1. `sha3_256` -2. `sha3_512` -3. `keccak256` -4. `keccak512` - -Each function calculates the respective digest of a given message. - -- **Parameters:** - - - `msg` (string): The message to calculate the digest - -- **Returns:** A table containing functions to get digest in different formats. - - `asBytes()`: The digest as byte table. - - `asHex()`: The digest as string in hexadecimal format. - - `asString()`: The digest as string format. - -Example: - -```lua - -local str = "ao" - -crypto.digest.sha3_256(str).asHex() -- 1bbe785577db997a394d5b4555eec9159cb51f235aec07514872d2d436c6e985 -crypto.digest.sha3_512(str).asHex() -- 0c29f053400cb1764ce2ec555f598f497e6fcd1d304ce0125faa03bb724f63f213538f41103072ff62ddee701b52c73e621ed4d2254a3e5e9a803d83435b704d -crypto.digest.keccak256(str).asHex() -- 76da52eec05b749b99d6e62bb52333c1569fe75284e6c82f3de12a4618be00d6 -crypto.digest.keccak512(str).asHex() -- 046fbfad009a12cef9ff00c2aac361d004347b2991c1fa80fba5582251b8e0be8def0283f45f020d4b04ff03ead9f6e7c43cc3920810c05b33b4873b99affdea - -``` - -## Blake2b - -Calculates the Blake2b digest of a given message. - -- **Parameters:** - - - `data` (string): The data to be hashed. - - `outlen` (number): The length of the output hash (optional) **default is 64**. - - `key` (string): The key to be used for hashing (optional) **default is ""**. - -- **Returns:** A table containing functions to get digest in different formats. - - `asBytes()`: The digest as byte table. - - `asHex()`: The digest as string in hexadecimal format. - - `asString()`: The digest as string format. - -Example: - -```lua -local str = "ao" - -crypto.digest.blake2b(str).asHex() -- 576701fd79a126f2c414ef94adf1117c88943700f312679d018c29c378b2c807a3412b4e8d51e191c48fb5f5f54bf1bca29a714dda166797b3baf9ead862ae1d -crypto.digest.blake2b(str, 32).asHex() -- 7050811afc947ba7190bb3c0a7b79b4fba304a0de61d529c8a35bdcbbb5544f4 -crypto.digest.blake2b(str, 32, "secret_key").asHex() -- 203c101980fdf6cf24d78879f2e3db86d73d91f7d60960b642022cd6f87408f8 -``` - ---- - -# Ciphers - -## AES - -The Advanced Encryption Standard (AES) is a symmetric block cipher used to encrypt sensitive information. It has two functions encrypt and decrypt. - -### Encrypt - -Encrypts a given message using the AES algorithm. - -- **Parameters:** - - - `data` (string): The data to be encrypted. - - `key` (string): The key to be used for encryption. - - `iv` (string) optional: The initialization vector to be used for encryption. **default is ""** - - `mode` (string) optional: The mode of operation to be used for encryption. **default is "CBC"**. Available modes are `CBC`, `ECB`, `CFB`, `OFB`, `CTR`. - - `keyLength` (number) optional: The length of the key to use for encryption. **default is 128**. - -- **Returns:** A table containing functions to get encrypted data in different formats. - - `asBytes()`: The encrypted data as byte table. - - `asHex()`: The encrypted data as string in hexadecimal format. - - `asString()`: The encrypted data as string format. - -## Decrypt - -Decrypts a given message using the AES algorithm. - -- **Parameters:** - - - `cipher` (string): Hex Encoded encrypted data. - - `key` (string): The key to be used for decryption. - - `iv` (string) optional: The initialization vector to be used for decryption. **default is ""** - - `mode` (string) optional: The mode of operation to be used for decryption. **default is "CBC"**. Available modes are `CBC`, `ECB`, `CFB`, `OFB`, `CTR`. - - `keyLength` (number) optional: The length of the key to use for decryption. **default is 128**. - -- **Returns:** A table containing functions to get decrypted data in different formats. - - `asBytes()`: The decrypted data as byte table. - - `asHex()`: The decrypted data as string in hexadecimal format. - - `asString()`: The decrypted data as string format. - -Example: - -```lua -local str = "ao" - -local iv = "super_secret_shh" -local key_128 = "super_secret_shh" - -local encrypted = crypto.cipher.aes.encrypt("ao", key, iv).asHex() -- A3B9E6E1FBD9D46930E5F76807C84B8E -local decrypted = crypto.cipher.aes.decrypt(encrypted, key, iv).asHex() -- 616F0000000000000000000000000000 - -crypto.utils.hex.hexToString(decrypted) -- ao - -``` - -## ISSAC Cipher - -ISAAC is a cryptographically secure pseudo-random number generator (CSPRNG) and stream cipher. It has the following functions - -1. `seedIsaac`: Seeds the ISAAC cipher with a given seed. -2. `getRandomChar`: Generates a random character using the ISAAC cipher. -3. `random`: Generates a random number between a given range using the ISAAC cipher. -4. `getRandom`: Generates a random number using the ISAAC cipher. -5. `encrypt`: Encrypts a given message using the ISAAC cipher. -6. `decrypt`: Decrypts a given message using the ISAAC cipher. - -### Encrypt - -Encrypts a given message using the ISAAC cipher. - -- **Parameters:** - - `msg` (string): The message to be encrypted. - - `key` (string): The key to be used for encryption. -- **Returns:** A table containing functions to get encrypted data in different formats. - - `asBytes()`: The encrypted data as byte table. - - `asHex()`: The encrypted data as string in hexadecimal format. - - `asString()`: The encrypted data as string format. - -### Decrypt - -Decrypts a given message using the ISAAC cipher. - -- **Parameters:** - - `cipher` (string): Hex Encoded encrypted data. - - `key` (string): Key to be used for decryption. -- **Returns:** A table containing functions to get decrypted data in different formats. - - `asBytes()`: The decrypted data as byte table. - - `asHex()`: The decrypted data as string in hexadecimal format. - - `asString()`: The decrypted data as string format. - -Example: - -```lua -local message = "ao"; -local key = "secret_key"; - -local encrypted = crypto.cipher.issac.encrypt(message, key) -local decrypted = crypto.cipher.issac.decrypt(encrypted.asString(), key) -- ao - - -encrypted.asHex() -- 7851 -``` - -### random - -Generates a random number using the ISAAC cipher. - -- **Parameters:** - - `min` (number) optional: The minimum value of the random number. **defaults to 0**. - - `max` (number) optional: The maximum value of the random number. **defaults to 2^31 - 1**. - - `seed` (string) optional: The seed to be used for generating the random number. **defaults to math.random(0,2^32 - 1)**. -- **Returns:** A random number between the given range. - -Example: - -```lua -crypto.cipher.issac.random(0, 100) -- 42 -``` - -## Morus Cipher - -MORUS is a high-performance authenticated encryption algorithm submitted to the CAESAR competition, and recently selected as a finalist. - -### Encrypt - -Encrypts a given message using the MORUS cipher. - -- **Parameters:** - - `key` (string): The encryption key (16 or 32-byte string). - - `iv` (string): The nonce or initial value (16-byte string). - - `msg` (string): The message to encrypt (variable length string). - - `ad` (string) optional: The additional data (variable length string). **defaults to ""**. -- **Returns:** A table containing functions to get encrypted data in different formats. - - `asBytes()`: The encrypted data as byte table. - - `asHex()`: The encrypted data as string in hexadecimal format. - - `asString()`: The encrypted data as string format. - -### Decrypt - -Decrypts a given message using the MORUS cipher. - -- **Parameters:** - - `key` (string): The encryption key (16 or 32-byte string). - - `iv` (string): The nonce or initial value (16-byte string). - - `cipher` (string): The encrypted message (variable length string). - - `adLen` (number) optional: The length of the additional data (variable length string). **defaults to 0**. -- **Returns:** A table containing functions to get decrypted data in different formats. - - `asBytes()`: The decrypted data as byte table. - - `asHex()`: The decrypted data as string in hexadecimal format. - - `asString()`: The decrypted data as string format. - -Example: - -```lua -local m = "ao" -local k = "super_secret_shh" -local iv = "0000000000000000" -local ad= "" - -local e = crypto.cipher.morus.encrypt(k, iv, m, ad) -local d = crypto.cipher.morus.decrypt(k, iv, e.asString(), #ad) -- ao - -e.asHex() -- 514ed31473d8fb0b76c6cbb17af35ed01d0a -``` - -## NORX Cipher - -NORX is an authenticated encryption scheme with associated data that was selected, along with 14 other primitives, for the third phase of the ongoing CAESAR competition. It is based on the sponge construction and relies on a simple permutation that allows efficient and versatile implementations. - -### Encrypt - -Encrypts a given message using the NORX cipher. - -- **Parameters:** - - `key` (string): The encryption key (32-byte string). - - `nonce` (string): The nonce or initial value (32-byte string). - - `plain` (string): The message to encrypt (variable length string). - - `header` (string) optional: The additional data (variable length string). **defaults to ""**. - - `trailer` (string) optional: The additional data (variable length string). **defaults to ""**. -- **Returns:** A table containing functions to get encrypted data in different formats. - - `asBytes()`: The encrypted data as byte table. - - `asHex()`: The encrypted data as string in hexadecimal format. - - `asString()`: The encrypted data as string format. - -### Decrypt - -Decrypts a given message using the NORX cipher. - -- **Parameters:** - - `key` (string): The encryption key (32-byte string). - - `nonce` (string): The nonce or initial value (32-byte string). - - `crypted` (string): The encrypted message (variable length string). - - `header` (string) optional: The additional data (variable length string). **defaults to ""**. - - `trailer` (string) optional: The additional data (variable length string). **defaults to ""**. -- **Returns:** A table containing functions to get decrypted data in different formats. - - `asBytes()`: The decrypted data as byte table. - - `asHex()`: The decrypted data as string in hexadecimal format. - - `asString()`: The decrypted data as string format. - -Example: - -```lua -local key = "super_duper_secret_password_shhh" -local nonce = "00000000000000000000000000000000" - -local data = "ao" - --- Header and trailer are optional -local header, trailer = data, data - -local encrypted = crypto.cipher.norx.encrypt(key, nonce, data, header, trailer).asString() -local decrypted = crypto.cipher.norx.decrypt(key, nonce, encrypted, header, trailer) -- ao - -local authTag = encrypted:sub(#encrypted-32+1) - -crypto.utils.hex.stringToHex(encrypted) -- 0bb35a06938e6541eccd4440adb7b46118535f60b09b4adf378807a53df19fc4ea28 -crypto.utils.hex.stringToHex(authTag) -- 5a06938e6541eccd4440adb7b46118535f60b09b4adf378807a53df19fc4ea28 -``` - ---- - -# Random Number Generators - -The module contains a random number generator using ISAAC which is a cryptographically secure pseudo-random number generator (CSPRNG) and stream cipher. - -- **Parameters:** - - `min` (number) optional: The minimum value of the random number. **defaults to 0**. - - `max` (number) optional: The maximum value of the random number. **defaults to 2^31 - 1**. - - `seed` (string) optional: The seed to be used for generating the random number. **defaults to math.random(0,2^32 - 1)**. -- **Returns:** A random number between the given range. - -Example: - -```lua -crypto.random.(0, 100, "seed") -- 42 -``` - ---- - -# MACs - -## HMAC - -The Hash-based Message Authentication Code (HMAC) is a mechanism for message authentication using cryptographic hash functions. HMAC can be used with any iterative cryptographic hash function, e.g., MD5, SHA-1, in combination with a secret shared key. - -The modules exposes a function called `createHmac` which is used to create a HMAC instance. - -- **Parameters:** - - `data` (Stream): The data to be hashed. - - `key` (Array): The key to be used for hashing. - - `algorithm` (string) optional: The algorithm to be used for hashing. **default is "sha256"**. Available algorithms are "sha1", "sha256". **default is "sha1"**. -- **Returns:** A table containing functions to get HMAC in different formats. - - `asBytes()`: The HMAC as byte table. - - `asHex()`: The HMAC as string in hexadecimal format. - - `asString()`: The HMAC as string format. - -Example: - -```lua -local data = crypto.utils.stream.fromString("ao") -local key = crypto.utils.array.fromString("super_secret_key") - -crypto.mac.createHmac(data, key).asHex() -- 3966f45acb53f7a1a493bae15afecb1a204fa32d -crypto.mac.createHmac(data, key, "sha256").asHex() -- 542da02a324155d688c7689669ff94c6a5f906892aa8eccd7284f210ac66e2a7 -``` - ---- - -# KDFs - -## PBKDF2 - -The Password-Based Key Derivation Function 2 (PBKDF2) applies a pseudorandom function, such as hash-based message authentication code (HMAC), to the input password or passphrase along with a salt value and repeats the process many times to produce a derived key, which can then be used as a cryptographic key in subsequent operations. - -- **Parameters:** - - `password` (Array): The password to derive the key from. - - `salt` (Array): The salt to use. - - `iterations` (number): The number of iterations to perform. - - `keyLen` (number): The length of the key to derive. - - `digest` (string) optional: The digest algorithm to use. **default is "sha1"**. Available algorithms are "sha1", "sha256". -- **Returns:** A table containing functions to get derived key in different formats. - - `asBytes()`: The derived key as byte table. - - `asHex()`: The derived key as string in hexadecimal format. - - `asString()`: The derived key as string format. - -Example: - -```lua -local salt = crypto.utils.array.fromString("salt") -local password = crypto.utils.array.fromString("password") -local iterations = 4 -local keyLen = 16 - -local res = crypto.kdf.pbkdf2(password, salt, iterations, keyLen).asHex() -- C4C21BF2BBF61541408EC2A49C89B9C6 -``` - ---- - -# Utilities - -## Array - -Example Usage: - -```lua - -local arr = crypto.utils.array - -arr.fromString("ao") -- Array -arr.toString(arr.fromString("ao")) -- ao - -arr.fromHex("616f") -- Array -arr.toHex(arr.fromHex("616f")) -- 616f - -arr.concat(arr.fromString("a"), arr.fromString("o")) -- Array -arr.truncate(arr.fromString("ao"), 1) -- Array - -arr.XOR(arr.fromString("a"), arr.fromString("o")) -- Array - -arr.substitute(arr.fromString("a"), arr.fromString("o")) -- Array -arr.permute(arr.fromString("a"), arr.fromString("o")) -- Array - -arr.copy(arr.fromString("ao")) -- Array -arr.slice(arr.fromString("ao"), 0, 1) -- Array -``` - -### `size` - -Returns the size of the array. - -- **Parameters:** - - `arr` (Array): The array to get the size of. -- **Returns:** The size of the array. - -### `fromString` - -Creates an array from a string. - -- **Parameters:** - - `str` (string): The string to create the array from. -- **Returns:** The array created from the string. - -### `toString` - -Converts an array to a string. - -- **Parameters:** - - `arr` (Array): The array to convert to a string. -- **Returns:** The array as a string. - -### `fromStream` - -Creates an array from a stream. - -- **Parameters:** - - `stream` (Stream): The stream to create the array from. -- **Returns:** The array created from the stream. - -### `readFromQueue` - -Reads data from a queue and stores it in the array. - -- **Parameters:** - - `queue` (Queue): The queue to read data from. - - `size` (number): The size of the data to read. -- **Returns:** The array containing the data read from the queue. - -### `writeToQueue` - -Writes data from the array to a queue. - -- **Parameters:** - - `queue` (Queue): The queue to write data to. - - `array` (Array): The array to write data from. -- **Returns:** None - -### `toStream` - -Converts an array to a stream. - -- **Parameters:** - - `arr` (Array): The array to convert to a stream. -- **Returns:** (Stream) The array as a stream. - -### `fromHex` - -Creates an array from a hexadecimal string. - -- **Parameters:** - - `hex` (string): The hexadecimal string to create the array from. -- **Returns:** The array created from the hexadecimal string. - -### `toHex` - -Converts an array to a hexadecimal string. - -- **Parameters:** - - `arr` (Array): The array to convert to a hexadecimal string. -- **Returns:** The array as a hexadecimal string. - -### `concat` - -Concatenates two arrays. - -- **Parameters:** - - `a` (Array): The array to concatenate with. - - `b` (Array): The array to concatenate. -- **Returns:** The concatenated array. - -### `truncate` - -Truncates an array to a given length. - -- **Parameters:** - - `a` (Array): The array to truncate. - - `newSize` (number): The new size of the array. -- **Returns:** The truncated array. - -### `XOR` - -Performs a bitwise XOR operation on two arrays. - -- **Parameters:** - - `a` (Array): The first array. - - `b` (Array): The second array. -- **Returns:** The result of the XOR operation. - -### `substitute` - -Creates a new array with keys of first array and values of second - -- **Parameters:** - - `input` (Array): The array to substitute. - - `sbox` (Array): The array to substitute with. -- **Returns:** The substituted array. - -### `permute` - -Creates a new array with keys of second array and values of first array. - -- **Parameters:** - - `input` (Array): The array to permute. - - `pbox` (Array): The array to permute with. -- **Returns:** The permuted array. - -### `copy` - -Creates a copy of an array. - -- **Parameters:** - - `input` (Array): The array to copy. -- **Returns:** The copied array. - -### `slice` - -Creates a slice of an array. - -- **Parameters:** - - `input` (Array): The array to slice. - - `start` (number): The start index of the slice. - - `stop` (number): The end index of the slice. -- **Returns:** The sliced array. - ---- - -## Stream - -Stream is a data structure that represents a sequence of bytes. It is used to store and manipulate data in a streaming fashion. - -Example Usage: - -```lua -local stream = crypto.utils.stream - -local str = "ao" -local arr = {97, 111} - -stream.fromString(str) -- Stream -stream.toString(stream.fromString(str)) -- ao - -stream.fromArray(arr) -- Stream -stream.toArray(stream.fromArray(arr)) -- {97, 111} - -stream.fromHex("616f") -- Stream -stream.toHex(stream.fromHex("616f")) -- 616f -``` - -### `fromString` - -Creates a stream from a string. - -- **Parameters:** - - `str` (string): The string to create the stream from. -- **Returns:** The stream created from the string. - -### `toString` - -Converts a stream to a string. - -- **Parameters:** - - `stream` (Stream): The stream to convert to a string. -- **Returns:** The stream as a string. - -### `fromArray` - -Creates a stream from an array. - -- **Parameters:** - - `arr` (Array): The array to create the stream from. -- **Returns:** The stream created from the array. - -### `toArray` - -Converts a stream to an array. - -- **Parameters:** - - `stream` (Stream): The stream to convert to an array. -- **Returns:** The stream as an array. - -### `fromHex` - -Creates a stream from a hexadecimal string. - -- **Parameters:** - - `hex` (string): The hexadecimal string to create the stream from. -- **Returns:** The stream created from the hexadecimal string. - -### `toHex` - -Converts a stream to a hexadecimal string. - -- **Parameters:** - - `stream` (Stream): The stream to convert to a hexadecimal string. -- **Returns:** The stream as a hexadecimal string. - ---- - -## Hex - -Example Usage: - -```lua -local hex = crypto.utils.hex - -hex.hexToString("616f") -- ao -hex.stringToHex("ao") -- 616f -``` - -### `hexToString` - -Converts a hexadecimal string to a string. - -- **Parameters:** - - `hex` (string): The hexadecimal string to convert to a string. -- **Returns:** The hexadecimal string as a string. - -### `stringToHex` - -Converts a string to a hexadecimal string. - -- **Parameters:** - - `str` (string): The string to convert to a hexadecimal string. -- **Returns:** The string as a hexadecimal string. - ---- - -## Queue - -Queue is a data structure that represents a sequence of elements. It is used to store and manipulate data in a first-in, first-out (FIFO) fashion. - -Example Usage: - -```lua -local q = crypto.utils.queue() - -q.push(1) -q.push(2) -q.pop() -- 1 -q.size() -- 1 -q.getHead() -- 2 -q.getTail() -- 2 -q.reset() -``` - -### `push` - -Pushes an element to the queue. - -- **Parameters:** - - `queue` (Queue): The queue to push the element to. - - `element` (any): The element to push to the queue. -- **Returns:** None - -### `pop` - -Pops an element from the queue. - -- **Parameters:** - - `queue` (Queue): The queue to pop the element from. - - `element` (any): The element to pop from the queue. -- **Returns:** The popped element. - -### `size` - -Returns the size of the queue. - -- **Parameters:** None -- **Returns:** The size of the queue. - -### `getHead` - -Returns the head of the queue. - -- **Parameters:** None -- **Returns:** The head of the queue. - -### `getTail` - -Returns the tail of the queue. - -- **Parameters:** None -- **Returns:** The tail of the queue. - -### `reset` - -Resets the queue. - -- **Parameters:** None - ---- - -## Conventions and Requirements - -1. The module should be imported using `local crypto = require('.crypto')`. -2. The module should be used in a Lua environment. - ---- - -## License - -MIT diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes.lua deleted file mode 100644 index 5ed1f5d..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes.lua +++ /dev/null @@ -1,142 +0,0 @@ -local Stream = require(".crypto.util.stream") -local Hex = require(".crypto.util.hex") -local Array = require(".crypto.util.array") - --- Ciphers -local AES128Cipher = require(".crypto.cipher.aes128") -local AES192Cipher = require(".crypto.cipher.aes192") -local AES256Cipher = require(".crypto.cipher.aes256") - --- Modes -local CBCMode = require(".crypto.cipher.mode.cbc") -local ECBMode = require(".crypto.cipher.mode.ecb") -local CFBMode = require(".crypto.cipher.mode.cfb") -local OFBMode = require(".crypto.cipher.mode.ofb") -local CTRMode = require(".crypto.cipher.mode.ctr") - --- Padding -local ZeroPadding = require(".crypto.padding.zero") - -local public = {} - -local getBlockCipher = function(keyLength) - if keyLength == 128 then - return AES128Cipher - elseif keyLength == 192 then - return AES192Cipher - elseif keyLength == 256 then - return AES256Cipher - elseif keyLength == nil then - return AES128Cipher - else - return nil - end -end - -local getMode = function(mode) - if mode == "CBC" then - return CBCMode - elseif mode == "ECB" then - return ECBMode - elseif mode == "CFB" then - return CFBMode - elseif mode == "OFB" then - return OFBMode - elseif mode == "CTR" then - return CTRMode - else - return nil - end -end - - ---- Encrypts the given data using AES encryption. ---- @param data string - The data to be encrypted. ---- @param key string - The key to use for encryption. ---- @param iv? string (optional) - The initialization vector to use for encryption. Defaults to 16 null bytes. ---- @param mode? string (optional) - The mode to use for encryption. Defaults to "CBC". ---- @param keyLength? number (optional) - The length of the key to use for encryption. Defaults to 128. ---- @returns table - A table containing the encrypted data in bytes, hex, and string formats. -public.encrypt = function(data, key, iv, mode, keyLength) - local d = Array.fromString(data) - local k = Array.fromString(key) - local _iv = iv ~= nil and Array.fromString(iv) or Array.fromHex("00000000000000000000000000000000") - - local cipherMode = getMode(mode) or CBCMode - local blockCipher = getBlockCipher(keyLength) or AES128Cipher - - local cipher = cipherMode.Cipher() - .setKey(k) - .setBlockCipher(blockCipher) - .setPadding(ZeroPadding); - - - local cipherOutput = cipher - .init() - .update(Stream.fromArray(_iv)) - .update(Stream.fromArray(d)) - .finish() - - local results = {} - - results.asBytes = function() - return cipherOutput.asBytes() - end - - results.asHex = function() - return cipherOutput.asHex() - end - - results.asString = function() - return cipherOutput.asString() - end - - return results -end - ---- Decrypts the given data using AES decryption. ---- @param cipher string - The hex encoded cipher to be decrypted. ---- @param key string - The key to use for decryption. ---- @param iv? string (optional) - The initialization vector to use for decryption. Defaults to 16 null bytes. ---- @param mode? string (optional) - The mode to use for decryption. Defaults to "CBC". ---- @param keyLength? number (optional) - The length of the key to use for decryption. Defaults to 128. -public.decrypt = function(cipher, key, iv, mode, keyLength) - local cipherText = Array.fromHex(cipher) - local k = Array.fromString(key) - local _iv = iv ~= nil and Array.fromString(iv) or Array.fromHex("00000000000000000000000000000000") - - local cipherMode = getMode(mode) or CBCMode - local blockCipher = getBlockCipher(keyLength) or AES128Cipher - - - local decipher = cipherMode.Decipher() - .setKey(k) - .setBlockCipher(blockCipher) - .setPadding(ZeroPadding); - - - local plainOutput = decipher - .init() - .update(Stream.fromArray(_iv)) - .update(Stream.fromArray(cipherText)) - .finish() - - local results = {} - - results.asBytes = function() - return plainOutput.asBytes() - end - - results.asHex = function() - return plainOutput.asHex() - end - - results.asString = function() - return plainOutput.asString() - end - - return results -end - - -return public diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes128.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes128.lua deleted file mode 100644 index 061598e..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes128.lua +++ /dev/null @@ -1,415 +0,0 @@ -local Array = require(".crypto.util.array"); -local Bit = require(".crypto.util.bit"); - -local XOR = Bit.bxor; - -local SBOX = { - [0] = 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, - 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, - 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, - 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, - 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, - 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, - 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, - 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, - 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, - 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, - 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, - 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, - 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, - 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, - 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, - 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16}; - -local ISBOX = { - [0] = 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, - 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, - 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, - 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, - 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, - 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, - 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, - 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, - 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, - 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, - 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, - 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, - 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, - 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, - 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, - 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D}; - -local ROW_SHIFT = { 1, 6, 11, 16, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, }; -local IROW_SHIFT = { 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, 16, 13, 10, 7, 4, }; - -local ETABLE = { - [0] = 0x01, 0x03, 0x05, 0x0F, 0x11, 0x33, 0x55, 0xFF, 0x1A, 0x2E, 0x72, 0x96, 0xA1, 0xF8, 0x13, 0x35, - 0x5F, 0xE1, 0x38, 0x48, 0xD8, 0x73, 0x95, 0xA4, 0xF7, 0x02, 0x06, 0x0A, 0x1E, 0x22, 0x66, 0xAA, - 0xE5, 0x34, 0x5C, 0xE4, 0x37, 0x59, 0xEB, 0x26, 0x6A, 0xBE, 0xD9, 0x70, 0x90, 0xAB, 0xE6, 0x31, - 0x53, 0xF5, 0x04, 0x0C, 0x14, 0x3C, 0x44, 0xCC, 0x4F, 0xD1, 0x68, 0xB8, 0xD3, 0x6E, 0xB2, 0xCD, - 0x4C, 0xD4, 0x67, 0xA9, 0xE0, 0x3B, 0x4D, 0xD7, 0x62, 0xA6, 0xF1, 0x08, 0x18, 0x28, 0x78, 0x88, - 0x83, 0x9E, 0xB9, 0xD0, 0x6B, 0xBD, 0xDC, 0x7F, 0x81, 0x98, 0xB3, 0xCE, 0x49, 0xDB, 0x76, 0x9A, - 0xB5, 0xC4, 0x57, 0xF9, 0x10, 0x30, 0x50, 0xF0, 0x0B, 0x1D, 0x27, 0x69, 0xBB, 0xD6, 0x61, 0xA3, - 0xFE, 0x19, 0x2B, 0x7D, 0x87, 0x92, 0xAD, 0xEC, 0x2F, 0x71, 0x93, 0xAE, 0xE9, 0x20, 0x60, 0xA0, - 0xFB, 0x16, 0x3A, 0x4E, 0xD2, 0x6D, 0xB7, 0xC2, 0x5D, 0xE7, 0x32, 0x56, 0xFA, 0x15, 0x3F, 0x41, - 0xC3, 0x5E, 0xE2, 0x3D, 0x47, 0xC9, 0x40, 0xC0, 0x5B, 0xED, 0x2C, 0x74, 0x9C, 0xBF, 0xDA, 0x75, - 0x9F, 0xBA, 0xD5, 0x64, 0xAC, 0xEF, 0x2A, 0x7E, 0x82, 0x9D, 0xBC, 0xDF, 0x7A, 0x8E, 0x89, 0x80, - 0x9B, 0xB6, 0xC1, 0x58, 0xE8, 0x23, 0x65, 0xAF, 0xEA, 0x25, 0x6F, 0xB1, 0xC8, 0x43, 0xC5, 0x54, - 0xFC, 0x1F, 0x21, 0x63, 0xA5, 0xF4, 0x07, 0x09, 0x1B, 0x2D, 0x77, 0x99, 0xB0, 0xCB, 0x46, 0xCA, - 0x45, 0xCF, 0x4A, 0xDE, 0x79, 0x8B, 0x86, 0x91, 0xA8, 0xE3, 0x3E, 0x42, 0xC6, 0x51, 0xF3, 0x0E, - 0x12, 0x36, 0x5A, 0xEE, 0x29, 0x7B, 0x8D, 0x8C, 0x8F, 0x8A, 0x85, 0x94, 0xA7, 0xF2, 0x0D, 0x17, - 0x39, 0x4B, 0xDD, 0x7C, 0x84, 0x97, 0xA2, 0xFD, 0x1C, 0x24, 0x6C, 0xB4, 0xC7, 0x52, 0xF6, 0x01}; - -local LTABLE = { - [0] = 0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1A, 0xC6, 0x4B, 0xC7, 0x1B, 0x68, 0x33, 0xEE, 0xDF, 0x03, - 0x64, 0x04, 0xE0, 0x0E, 0x34, 0x8D, 0x81, 0xEF, 0x4C, 0x71, 0x08, 0xC8, 0xF8, 0x69, 0x1C, 0xC1, - 0x7D, 0xC2, 0x1D, 0xB5, 0xF9, 0xB9, 0x27, 0x6A, 0x4D, 0xE4, 0xA6, 0x72, 0x9A, 0xC9, 0x09, 0x78, - 0x65, 0x2F, 0x8A, 0x05, 0x21, 0x0F, 0xE1, 0x24, 0x12, 0xF0, 0x82, 0x45, 0x35, 0x93, 0xDA, 0x8E, - 0x96, 0x8F, 0xDB, 0xBD, 0x36, 0xD0, 0xCE, 0x94, 0x13, 0x5C, 0xD2, 0xF1, 0x40, 0x46, 0x83, 0x38, - 0x66, 0xDD, 0xFD, 0x30, 0xBF, 0x06, 0x8B, 0x62, 0xB3, 0x25, 0xE2, 0x98, 0x22, 0x88, 0x91, 0x10, - 0x7E, 0x6E, 0x48, 0xC3, 0xA3, 0xB6, 0x1E, 0x42, 0x3A, 0x6B, 0x28, 0x54, 0xFA, 0x85, 0x3D, 0xBA, - 0x2B, 0x79, 0x0A, 0x15, 0x9B, 0x9F, 0x5E, 0xCA, 0x4E, 0xD4, 0xAC, 0xE5, 0xF3, 0x73, 0xA7, 0x57, - 0xAF, 0x58, 0xA8, 0x50, 0xF4, 0xEA, 0xD6, 0x74, 0x4F, 0xAE, 0xE9, 0xD5, 0xE7, 0xE6, 0xAD, 0xE8, - 0x2C, 0xD7, 0x75, 0x7A, 0xEB, 0x16, 0x0B, 0xF5, 0x59, 0xCB, 0x5F, 0xB0, 0x9C, 0xA9, 0x51, 0xA0, - 0x7F, 0x0C, 0xF6, 0x6F, 0x17, 0xC4, 0x49, 0xEC, 0xD8, 0x43, 0x1F, 0x2D, 0xA4, 0x76, 0x7B, 0xB7, - 0xCC, 0xBB, 0x3E, 0x5A, 0xFB, 0x60, 0xB1, 0x86, 0x3B, 0x52, 0xA1, 0x6C, 0xAA, 0x55, 0x29, 0x9D, - 0x97, 0xB2, 0x87, 0x90, 0x61, 0xBE, 0xDC, 0xFC, 0xBC, 0x95, 0xCF, 0xCD, 0x37, 0x3F, 0x5B, 0xD1, - 0x53, 0x39, 0x84, 0x3C, 0x41, 0xA2, 0x6D, 0x47, 0x14, 0x2A, 0x9E, 0x5D, 0x56, 0xF2, 0xD3, 0xAB, - 0x44, 0x11, 0x92, 0xD9, 0x23, 0x20, 0x2E, 0x89, 0xB4, 0x7C, 0xB8, 0x26, 0x77, 0x99, 0xE3, 0xA5, - 0x67, 0x4A, 0xED, 0xDE, 0xC5, 0x31, 0xFE, 0x18, 0x0D, 0x63, 0x8C, 0x80, 0xC0, 0xF7, 0x70, 0x07}; - -local MIXTABLE = { - 0x02, 0x03, 0x01, 0x01, - 0x01, 0x02, 0x03, 0x01, - 0x01, 0x01, 0x02, 0x03, - 0x03, 0x01, 0x01, 0x02}; - -local IMIXTABLE = { - 0x0E, 0x0B, 0x0D, 0x09, - 0x09, 0x0E, 0x0B, 0x0D, - 0x0D, 0x09, 0x0E, 0x0B, - 0x0B, 0x0D, 0x09, 0x0E}; - -local RCON = { -[0] = 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, -0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, -0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, -0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, -0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, -0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, -0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, -0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, -0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, -0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, -0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, -0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, -0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, -0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, -0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, -0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d}; - - -local GMUL = function(A, B) - if(A == 0x01) then return B; end - if(B == 0x01) then return A; end - if(A == 0x00) then return 0; end - if(B == 0x00) then return 0; end - - local LA = LTABLE[A]; - local LB = LTABLE[B]; - - local sum = LA + LB; - if (sum > 0xFF) then sum = sum - 0xFF; end - - return ETABLE[sum]; -end - -local byteSub = Array.substitute; - -local shiftRow = Array.permute; - -local mixCol = function(i, mix) - local out = {}; - - local a, b, c, d; - - a = GMUL(i[ 1], mix[ 1]); - b = GMUL(i[ 2], mix[ 2]); - c = GMUL(i[ 3], mix[ 3]); - d = GMUL(i[ 4], mix[ 4]); - out[ 1] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 1], mix[ 5]); - b = GMUL(i[ 2], mix[ 6]); - c = GMUL(i[ 3], mix[ 7]); - d = GMUL(i[ 4], mix[ 8]); - out[ 2] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 1], mix[ 9]); - b = GMUL(i[ 2], mix[10]); - c = GMUL(i[ 3], mix[11]); - d = GMUL(i[ 4], mix[12]); - out[ 3] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 1], mix[13]); - b = GMUL(i[ 2], mix[14]); - c = GMUL(i[ 3], mix[15]); - d = GMUL(i[ 4], mix[16]); - out[ 4] = XOR(XOR(a, b), XOR(c, d)); - - - a = GMUL(i[ 5], mix[ 1]); - b = GMUL(i[ 6], mix[ 2]); - c = GMUL(i[ 7], mix[ 3]); - d = GMUL(i[ 8], mix[ 4]); - out[ 5] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 5], mix[ 5]); - b = GMUL(i[ 6], mix[ 6]); - c = GMUL(i[ 7], mix[ 7]); - d = GMUL(i[ 8], mix[ 8]); - out[ 6] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 5], mix[ 9]); - b = GMUL(i[ 6], mix[10]); - c = GMUL(i[ 7], mix[11]); - d = GMUL(i[ 8], mix[12]); - out[ 7] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 5], mix[13]); - b = GMUL(i[ 6], mix[14]); - c = GMUL(i[ 7], mix[15]); - d = GMUL(i[ 8], mix[16]); - out[ 8] = XOR(XOR(a, b), XOR(c, d)); - - - a = GMUL(i[ 9], mix[ 1]); - b = GMUL(i[10], mix[ 2]); - c = GMUL(i[11], mix[ 3]); - d = GMUL(i[12], mix[ 4]); - out[ 9] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 9], mix[ 5]); - b = GMUL(i[10], mix[ 6]); - c = GMUL(i[11], mix[ 7]); - d = GMUL(i[12], mix[ 8]); - out[10] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 9], mix[ 9]); - b = GMUL(i[10], mix[10]); - c = GMUL(i[11], mix[11]); - d = GMUL(i[12], mix[12]); - out[11] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 9], mix[13]); - b = GMUL(i[10], mix[14]); - c = GMUL(i[11], mix[15]); - d = GMUL(i[12], mix[16]); - out[12] = XOR(XOR(a, b), XOR(c, d)); - - - a = GMUL(i[13], mix[ 1]); - b = GMUL(i[14], mix[ 2]); - c = GMUL(i[15], mix[ 3]); - d = GMUL(i[16], mix[ 4]); - out[13] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[13], mix[ 5]); - b = GMUL(i[14], mix[ 6]); - c = GMUL(i[15], mix[ 7]); - d = GMUL(i[16], mix[ 8]); - out[14] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[13], mix[ 9]); - b = GMUL(i[14], mix[10]); - c = GMUL(i[15], mix[11]); - d = GMUL(i[16], mix[12]); - out[15] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[13], mix[13]); - b = GMUL(i[14], mix[14]); - c = GMUL(i[15], mix[15]); - d = GMUL(i[16], mix[16]); - out[16] = XOR(XOR(a, b), XOR(c, d)); - - return out; -end - -local keyRound = function(key, round) - local out = {}; - - out[ 1] = XOR(key[ 1], XOR(SBOX[key[14]], RCON[round])); - out[ 2] = XOR(key[ 2], SBOX[key[15]]); - out[ 3] = XOR(key[ 3], SBOX[key[16]]); - out[ 4] = XOR(key[ 4], SBOX[key[13]]); - - out[ 5] = XOR(out[ 1], key[ 5]); - out[ 6] = XOR(out[ 2], key[ 6]); - out[ 7] = XOR(out[ 3], key[ 7]); - out[ 8] = XOR(out[ 4], key[ 8]); - - out[ 9] = XOR(out[ 5], key[ 9]); - out[10] = XOR(out[ 6], key[10]); - out[11] = XOR(out[ 7], key[11]); - out[12] = XOR(out[ 8], key[12]); - - out[13] = XOR(out[ 9], key[13]); - out[14] = XOR(out[10], key[14]); - out[15] = XOR(out[11], key[15]); - out[16] = XOR(out[12], key[16]); - - return out; -end - -local keyExpand = function(key) - local keys = {}; - - local temp = key; - - keys[1] = temp; - - for i = 1, 10 do - temp = keyRound(temp, i); - keys[i + 1] = temp; - end - - return keys; - -end - -local addKey = Array.XOR; - - - -local AES = {}; - -AES.blockSize = 16; - -AES.encrypt = function(key, block) - - local keySchedule = keyExpand(key); - - --round 0 - block = addKey(block, keySchedule[1]); - - --round 1 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[2]); - - --round 2 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[3]); - - --round 3 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[4]); - - --round 4 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[5]); - - --round 5 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[6]); - - --round 6 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[7]); - - --round 7 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[8]); - - --round 8 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[9]); - - --round 9 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[10]); - - --round 10 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = addKey(block, keySchedule[11]); - - return block; - -end - -AES.decrypt = function(key, block) - - local keySchedule = keyExpand(key); - - --round 0 - block = addKey(block, keySchedule[11]); - - --round 1 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[10]); - block = mixCol(block, IMIXTABLE); - - --round 2 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[9]); - block = mixCol(block, IMIXTABLE); - - --round 3 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[8]); - block = mixCol(block, IMIXTABLE); - - --round 4 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[7]); - block = mixCol(block, IMIXTABLE); - - --round 5 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[6]); - block = mixCol(block, IMIXTABLE); - - --round 6 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[5]); - block = mixCol(block, IMIXTABLE); - - --round 7 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[4]); - block = mixCol(block, IMIXTABLE); - - --round 8 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[3]); - block = mixCol(block, IMIXTABLE); - - --round 9 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[2]); - block = mixCol(block, IMIXTABLE); - - --round 10 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[1]); - - return block; -end - -return AES; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes192.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes192.lua deleted file mode 100644 index 67eb603..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes192.lua +++ /dev/null @@ -1,462 +0,0 @@ - -local Array = require(".crypto.util.array"); -local Bit = require(".crypto.util.bit"); - -local XOR = Bit.bxor; - -local SBOX = { - [0] = 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, - 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, - 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, - 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, - 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, - 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, - 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, - 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, - 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, - 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, - 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, - 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, - 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, - 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, - 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, - 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16}; - -local ISBOX = { - [0] = 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, - 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, - 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, - 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, - 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, - 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, - 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, - 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, - 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, - 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, - 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, - 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, - 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, - 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, - 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, - 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D}; - -local ROW_SHIFT = { 1, 6, 11, 16, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, }; -local IROW_SHIFT = { 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, 16, 13, 10, 7, 4, }; - -local ETABLE = { - [0] = 0x01, 0x03, 0x05, 0x0F, 0x11, 0x33, 0x55, 0xFF, 0x1A, 0x2E, 0x72, 0x96, 0xA1, 0xF8, 0x13, 0x35, - 0x5F, 0xE1, 0x38, 0x48, 0xD8, 0x73, 0x95, 0xA4, 0xF7, 0x02, 0x06, 0x0A, 0x1E, 0x22, 0x66, 0xAA, - 0xE5, 0x34, 0x5C, 0xE4, 0x37, 0x59, 0xEB, 0x26, 0x6A, 0xBE, 0xD9, 0x70, 0x90, 0xAB, 0xE6, 0x31, - 0x53, 0xF5, 0x04, 0x0C, 0x14, 0x3C, 0x44, 0xCC, 0x4F, 0xD1, 0x68, 0xB8, 0xD3, 0x6E, 0xB2, 0xCD, - 0x4C, 0xD4, 0x67, 0xA9, 0xE0, 0x3B, 0x4D, 0xD7, 0x62, 0xA6, 0xF1, 0x08, 0x18, 0x28, 0x78, 0x88, - 0x83, 0x9E, 0xB9, 0xD0, 0x6B, 0xBD, 0xDC, 0x7F, 0x81, 0x98, 0xB3, 0xCE, 0x49, 0xDB, 0x76, 0x9A, - 0xB5, 0xC4, 0x57, 0xF9, 0x10, 0x30, 0x50, 0xF0, 0x0B, 0x1D, 0x27, 0x69, 0xBB, 0xD6, 0x61, 0xA3, - 0xFE, 0x19, 0x2B, 0x7D, 0x87, 0x92, 0xAD, 0xEC, 0x2F, 0x71, 0x93, 0xAE, 0xE9, 0x20, 0x60, 0xA0, - 0xFB, 0x16, 0x3A, 0x4E, 0xD2, 0x6D, 0xB7, 0xC2, 0x5D, 0xE7, 0x32, 0x56, 0xFA, 0x15, 0x3F, 0x41, - 0xC3, 0x5E, 0xE2, 0x3D, 0x47, 0xC9, 0x40, 0xC0, 0x5B, 0xED, 0x2C, 0x74, 0x9C, 0xBF, 0xDA, 0x75, - 0x9F, 0xBA, 0xD5, 0x64, 0xAC, 0xEF, 0x2A, 0x7E, 0x82, 0x9D, 0xBC, 0xDF, 0x7A, 0x8E, 0x89, 0x80, - 0x9B, 0xB6, 0xC1, 0x58, 0xE8, 0x23, 0x65, 0xAF, 0xEA, 0x25, 0x6F, 0xB1, 0xC8, 0x43, 0xC5, 0x54, - 0xFC, 0x1F, 0x21, 0x63, 0xA5, 0xF4, 0x07, 0x09, 0x1B, 0x2D, 0x77, 0x99, 0xB0, 0xCB, 0x46, 0xCA, - 0x45, 0xCF, 0x4A, 0xDE, 0x79, 0x8B, 0x86, 0x91, 0xA8, 0xE3, 0x3E, 0x42, 0xC6, 0x51, 0xF3, 0x0E, - 0x12, 0x36, 0x5A, 0xEE, 0x29, 0x7B, 0x8D, 0x8C, 0x8F, 0x8A, 0x85, 0x94, 0xA7, 0xF2, 0x0D, 0x17, - 0x39, 0x4B, 0xDD, 0x7C, 0x84, 0x97, 0xA2, 0xFD, 0x1C, 0x24, 0x6C, 0xB4, 0xC7, 0x52, 0xF6, 0x01}; - -local LTABLE = { - [0] = 0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1A, 0xC6, 0x4B, 0xC7, 0x1B, 0x68, 0x33, 0xEE, 0xDF, 0x03, - 0x64, 0x04, 0xE0, 0x0E, 0x34, 0x8D, 0x81, 0xEF, 0x4C, 0x71, 0x08, 0xC8, 0xF8, 0x69, 0x1C, 0xC1, - 0x7D, 0xC2, 0x1D, 0xB5, 0xF9, 0xB9, 0x27, 0x6A, 0x4D, 0xE4, 0xA6, 0x72, 0x9A, 0xC9, 0x09, 0x78, - 0x65, 0x2F, 0x8A, 0x05, 0x21, 0x0F, 0xE1, 0x24, 0x12, 0xF0, 0x82, 0x45, 0x35, 0x93, 0xDA, 0x8E, - 0x96, 0x8F, 0xDB, 0xBD, 0x36, 0xD0, 0xCE, 0x94, 0x13, 0x5C, 0xD2, 0xF1, 0x40, 0x46, 0x83, 0x38, - 0x66, 0xDD, 0xFD, 0x30, 0xBF, 0x06, 0x8B, 0x62, 0xB3, 0x25, 0xE2, 0x98, 0x22, 0x88, 0x91, 0x10, - 0x7E, 0x6E, 0x48, 0xC3, 0xA3, 0xB6, 0x1E, 0x42, 0x3A, 0x6B, 0x28, 0x54, 0xFA, 0x85, 0x3D, 0xBA, - 0x2B, 0x79, 0x0A, 0x15, 0x9B, 0x9F, 0x5E, 0xCA, 0x4E, 0xD4, 0xAC, 0xE5, 0xF3, 0x73, 0xA7, 0x57, - 0xAF, 0x58, 0xA8, 0x50, 0xF4, 0xEA, 0xD6, 0x74, 0x4F, 0xAE, 0xE9, 0xD5, 0xE7, 0xE6, 0xAD, 0xE8, - 0x2C, 0xD7, 0x75, 0x7A, 0xEB, 0x16, 0x0B, 0xF5, 0x59, 0xCB, 0x5F, 0xB0, 0x9C, 0xA9, 0x51, 0xA0, - 0x7F, 0x0C, 0xF6, 0x6F, 0x17, 0xC4, 0x49, 0xEC, 0xD8, 0x43, 0x1F, 0x2D, 0xA4, 0x76, 0x7B, 0xB7, - 0xCC, 0xBB, 0x3E, 0x5A, 0xFB, 0x60, 0xB1, 0x86, 0x3B, 0x52, 0xA1, 0x6C, 0xAA, 0x55, 0x29, 0x9D, - 0x97, 0xB2, 0x87, 0x90, 0x61, 0xBE, 0xDC, 0xFC, 0xBC, 0x95, 0xCF, 0xCD, 0x37, 0x3F, 0x5B, 0xD1, - 0x53, 0x39, 0x84, 0x3C, 0x41, 0xA2, 0x6D, 0x47, 0x14, 0x2A, 0x9E, 0x5D, 0x56, 0xF2, 0xD3, 0xAB, - 0x44, 0x11, 0x92, 0xD9, 0x23, 0x20, 0x2E, 0x89, 0xB4, 0x7C, 0xB8, 0x26, 0x77, 0x99, 0xE3, 0xA5, - 0x67, 0x4A, 0xED, 0xDE, 0xC5, 0x31, 0xFE, 0x18, 0x0D, 0x63, 0x8C, 0x80, 0xC0, 0xF7, 0x70, 0x07}; - -local MIXTABLE = { - 0x02, 0x03, 0x01, 0x01, - 0x01, 0x02, 0x03, 0x01, - 0x01, 0x01, 0x02, 0x03, - 0x03, 0x01, 0x01, 0x02}; - -local IMIXTABLE = { - 0x0E, 0x0B, 0x0D, 0x09, - 0x09, 0x0E, 0x0B, 0x0D, - 0x0D, 0x09, 0x0E, 0x0B, - 0x0B, 0x0D, 0x09, 0x0E}; - -local RCON = { -[0] = 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, -0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, -0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, -0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, -0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, -0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, -0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, -0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, -0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, -0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, -0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, -0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, -0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, -0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, -0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, -0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d}; - - -local GMUL = function(A, B) - if(A == 0x01) then return B; end - if(B == 0x01) then return A; end - if(A == 0x00) then return 0; end - if(B == 0x00) then return 0; end - - local LA = LTABLE[A]; - local LB = LTABLE[B]; - - local sum = LA + LB; - if (sum > 0xFF) then sum = sum - 0xFF; end - - return ETABLE[sum]; -end - -local byteSub = Array.substitute; - -local shiftRow = Array.permute; - -local mixCol = function(i, mix) - local out = {}; - - local a, b, c, d; - - a = GMUL(i[ 1], mix[ 1]); - b = GMUL(i[ 2], mix[ 2]); - c = GMUL(i[ 3], mix[ 3]); - d = GMUL(i[ 4], mix[ 4]); - out[ 1] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 1], mix[ 5]); - b = GMUL(i[ 2], mix[ 6]); - c = GMUL(i[ 3], mix[ 7]); - d = GMUL(i[ 4], mix[ 8]); - out[ 2] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 1], mix[ 9]); - b = GMUL(i[ 2], mix[10]); - c = GMUL(i[ 3], mix[11]); - d = GMUL(i[ 4], mix[12]); - out[ 3] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 1], mix[13]); - b = GMUL(i[ 2], mix[14]); - c = GMUL(i[ 3], mix[15]); - d = GMUL(i[ 4], mix[16]); - out[ 4] = XOR(XOR(a, b), XOR(c, d)); - - - a = GMUL(i[ 5], mix[ 1]); - b = GMUL(i[ 6], mix[ 2]); - c = GMUL(i[ 7], mix[ 3]); - d = GMUL(i[ 8], mix[ 4]); - out[ 5] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 5], mix[ 5]); - b = GMUL(i[ 6], mix[ 6]); - c = GMUL(i[ 7], mix[ 7]); - d = GMUL(i[ 8], mix[ 8]); - out[ 6] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 5], mix[ 9]); - b = GMUL(i[ 6], mix[10]); - c = GMUL(i[ 7], mix[11]); - d = GMUL(i[ 8], mix[12]); - out[ 7] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 5], mix[13]); - b = GMUL(i[ 6], mix[14]); - c = GMUL(i[ 7], mix[15]); - d = GMUL(i[ 8], mix[16]); - out[ 8] = XOR(XOR(a, b), XOR(c, d)); - - - a = GMUL(i[ 9], mix[ 1]); - b = GMUL(i[10], mix[ 2]); - c = GMUL(i[11], mix[ 3]); - d = GMUL(i[12], mix[ 4]); - out[ 9] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 9], mix[ 5]); - b = GMUL(i[10], mix[ 6]); - c = GMUL(i[11], mix[ 7]); - d = GMUL(i[12], mix[ 8]); - out[10] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 9], mix[ 9]); - b = GMUL(i[10], mix[10]); - c = GMUL(i[11], mix[11]); - d = GMUL(i[12], mix[12]); - out[11] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 9], mix[13]); - b = GMUL(i[10], mix[14]); - c = GMUL(i[11], mix[15]); - d = GMUL(i[12], mix[16]); - out[12] = XOR(XOR(a, b), XOR(c, d)); - - - a = GMUL(i[13], mix[ 1]); - b = GMUL(i[14], mix[ 2]); - c = GMUL(i[15], mix[ 3]); - d = GMUL(i[16], mix[ 4]); - out[13] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[13], mix[ 5]); - b = GMUL(i[14], mix[ 6]); - c = GMUL(i[15], mix[ 7]); - d = GMUL(i[16], mix[ 8]); - out[14] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[13], mix[ 9]); - b = GMUL(i[14], mix[10]); - c = GMUL(i[15], mix[11]); - d = GMUL(i[16], mix[12]); - out[15] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[13], mix[13]); - b = GMUL(i[14], mix[14]); - c = GMUL(i[15], mix[15]); - d = GMUL(i[16], mix[16]); - out[16] = XOR(XOR(a, b), XOR(c, d)); - - return out; -end - -local keyRound = function(key, round) - local i = (round - 1) * 24; - local out = key; - - out[25 + i] = XOR(key[ 1 + i], XOR(SBOX[key[22 + i]], RCON[round])); - out[26 + i] = XOR(key[ 2 + i], SBOX[key[23 + i]]); - out[27 + i] = XOR(key[ 3 + i], SBOX[key[24 + i]]); - out[28 + i] = XOR(key[ 4 + i], SBOX[key[21 + i]]); - - out[29 + i] = XOR(out[25 + i], key[ 5 + i]); - out[30 + i] = XOR(out[26 + i], key[ 6 + i]); - out[31 + i] = XOR(out[27 + i], key[ 7 + i]); - out[32 + i] = XOR(out[28 + i], key[ 8 + i]); - - out[33 + i] = XOR(out[29 + i], key[ 9 + i]); - out[34 + i] = XOR(out[30 + i], key[10 + i]); - out[35 + i] = XOR(out[31 + i], key[11 + i]); - out[36 + i] = XOR(out[32 + i], key[12 + i]); - - out[37 + i] = XOR(out[33 + i], key[13 + i]); - out[38 + i] = XOR(out[34 + i], key[14 + i]); - out[39 + i] = XOR(out[35 + i], key[15 + i]); - out[40 + i] = XOR(out[36 + i], key[16 + i]); - - out[41 + i] = XOR(out[37 + i], key[17 + i]); - out[42 + i] = XOR(out[38 + i], key[18 + i]); - out[43 + i] = XOR(out[39 + i], key[19 + i]); - out[44 + i] = XOR(out[40 + i], key[20 + i]); - - out[45 + i] = XOR(out[41 + i], key[21 + i]); - out[46 + i] = XOR(out[42 + i], key[22 + i]); - out[47 + i] = XOR(out[43 + i], key[23 + i]); - out[48 + i] = XOR(out[44 + i], key[24 + i]); - - return out; -end - -local keyExpand = function(key) - local bytes = Array.copy(key); - - for i = 1, 8 do - keyRound(bytes, i); - end - - local keys = {}; - - keys[ 1] = Array.slice(bytes, 1, 16); - keys[ 2] = Array.slice(bytes, 17, 32); - keys[ 3] = Array.slice(bytes, 33, 48); - keys[ 4] = Array.slice(bytes, 49, 64); - keys[ 5] = Array.slice(bytes, 65, 80); - keys[ 6] = Array.slice(bytes, 81, 96); - keys[ 7] = Array.slice(bytes, 97, 112); - keys[ 8] = Array.slice(bytes, 113, 128); - keys[ 9] = Array.slice(bytes, 129, 144); - keys[10] = Array.slice(bytes, 145, 160); - keys[11] = Array.slice(bytes, 161, 176); - keys[12] = Array.slice(bytes, 177, 192); - keys[13] = Array.slice(bytes, 193, 208); - - return keys; - -end - -local addKey = Array.XOR; - - - -local AES = {}; - -AES.blockSize = 16; - -AES.encrypt = function(key, block) - - local keySchedule = keyExpand(key); - - --round 0 - block = addKey(block, keySchedule[1]); - - --round 1 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[2]); - - --round 2 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[3]); - - --round 3 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[4]); - - --round 4 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[5]); - - --round 5 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[6]); - - --round 6 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[7]); - - --round 7 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[8]); - - --round 8 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[9]); - - --round 9 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[10]); - - --round 10 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[11]); - - --round 11 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[12]); - - --round 12 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = addKey(block, keySchedule[13]); - - return block; - -end - -AES.decrypt = function(key, block) - - local keySchedule = keyExpand(key); - - --round 0 - block = addKey(block, keySchedule[13]); - - --round 1 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[12]); - block = mixCol(block, IMIXTABLE); - - --round 2 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[11]); - block = mixCol(block, IMIXTABLE); - - --round 3 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[10]); - block = mixCol(block, IMIXTABLE); - - --round 4 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[9]); - block = mixCol(block, IMIXTABLE); - - --round 5 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[8]); - block = mixCol(block, IMIXTABLE); - - --round 6 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[7]); - block = mixCol(block, IMIXTABLE); - - --round 7 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[6]); - block = mixCol(block, IMIXTABLE); - - --round 8 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[5]); - block = mixCol(block, IMIXTABLE); - - --round 9 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[4]); - block = mixCol(block, IMIXTABLE); - - --round 10 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[3]); - block = mixCol(block, IMIXTABLE); - - --round 11 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[2]); - block = mixCol(block, IMIXTABLE); - - --round 12 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[1]); - - return block; -end - -return AES; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes256.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes256.lua deleted file mode 100644 index bc79e77..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/aes256.lua +++ /dev/null @@ -1,498 +0,0 @@ -local Array = require(".crypto.util.array"); -local Bit = require(".crypto.util.bit"); - -local XOR = Bit.bxor; - -local SBOX = { - [0] = 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, - 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, - 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, - 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, - 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, - 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, - 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, - 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, - 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, - 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, - 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, - 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, - 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, - 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, - 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, - 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16}; - -local ISBOX = { - [0] = 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, - 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, - 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, - 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, - 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, - 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, - 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, - 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, - 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, - 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, - 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, - 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, - 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, - 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, - 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, - 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D}; - -local ROW_SHIFT = { 1, 6, 11, 16, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, }; -local IROW_SHIFT = { 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, 16, 13, 10, 7, 4, }; - -local ETABLE = { - [0] = 0x01, 0x03, 0x05, 0x0F, 0x11, 0x33, 0x55, 0xFF, 0x1A, 0x2E, 0x72, 0x96, 0xA1, 0xF8, 0x13, 0x35, - 0x5F, 0xE1, 0x38, 0x48, 0xD8, 0x73, 0x95, 0xA4, 0xF7, 0x02, 0x06, 0x0A, 0x1E, 0x22, 0x66, 0xAA, - 0xE5, 0x34, 0x5C, 0xE4, 0x37, 0x59, 0xEB, 0x26, 0x6A, 0xBE, 0xD9, 0x70, 0x90, 0xAB, 0xE6, 0x31, - 0x53, 0xF5, 0x04, 0x0C, 0x14, 0x3C, 0x44, 0xCC, 0x4F, 0xD1, 0x68, 0xB8, 0xD3, 0x6E, 0xB2, 0xCD, - 0x4C, 0xD4, 0x67, 0xA9, 0xE0, 0x3B, 0x4D, 0xD7, 0x62, 0xA6, 0xF1, 0x08, 0x18, 0x28, 0x78, 0x88, - 0x83, 0x9E, 0xB9, 0xD0, 0x6B, 0xBD, 0xDC, 0x7F, 0x81, 0x98, 0xB3, 0xCE, 0x49, 0xDB, 0x76, 0x9A, - 0xB5, 0xC4, 0x57, 0xF9, 0x10, 0x30, 0x50, 0xF0, 0x0B, 0x1D, 0x27, 0x69, 0xBB, 0xD6, 0x61, 0xA3, - 0xFE, 0x19, 0x2B, 0x7D, 0x87, 0x92, 0xAD, 0xEC, 0x2F, 0x71, 0x93, 0xAE, 0xE9, 0x20, 0x60, 0xA0, - 0xFB, 0x16, 0x3A, 0x4E, 0xD2, 0x6D, 0xB7, 0xC2, 0x5D, 0xE7, 0x32, 0x56, 0xFA, 0x15, 0x3F, 0x41, - 0xC3, 0x5E, 0xE2, 0x3D, 0x47, 0xC9, 0x40, 0xC0, 0x5B, 0xED, 0x2C, 0x74, 0x9C, 0xBF, 0xDA, 0x75, - 0x9F, 0xBA, 0xD5, 0x64, 0xAC, 0xEF, 0x2A, 0x7E, 0x82, 0x9D, 0xBC, 0xDF, 0x7A, 0x8E, 0x89, 0x80, - 0x9B, 0xB6, 0xC1, 0x58, 0xE8, 0x23, 0x65, 0xAF, 0xEA, 0x25, 0x6F, 0xB1, 0xC8, 0x43, 0xC5, 0x54, - 0xFC, 0x1F, 0x21, 0x63, 0xA5, 0xF4, 0x07, 0x09, 0x1B, 0x2D, 0x77, 0x99, 0xB0, 0xCB, 0x46, 0xCA, - 0x45, 0xCF, 0x4A, 0xDE, 0x79, 0x8B, 0x86, 0x91, 0xA8, 0xE3, 0x3E, 0x42, 0xC6, 0x51, 0xF3, 0x0E, - 0x12, 0x36, 0x5A, 0xEE, 0x29, 0x7B, 0x8D, 0x8C, 0x8F, 0x8A, 0x85, 0x94, 0xA7, 0xF2, 0x0D, 0x17, - 0x39, 0x4B, 0xDD, 0x7C, 0x84, 0x97, 0xA2, 0xFD, 0x1C, 0x24, 0x6C, 0xB4, 0xC7, 0x52, 0xF6, 0x01}; - -local LTABLE = { - [0] = 0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1A, 0xC6, 0x4B, 0xC7, 0x1B, 0x68, 0x33, 0xEE, 0xDF, 0x03, - 0x64, 0x04, 0xE0, 0x0E, 0x34, 0x8D, 0x81, 0xEF, 0x4C, 0x71, 0x08, 0xC8, 0xF8, 0x69, 0x1C, 0xC1, - 0x7D, 0xC2, 0x1D, 0xB5, 0xF9, 0xB9, 0x27, 0x6A, 0x4D, 0xE4, 0xA6, 0x72, 0x9A, 0xC9, 0x09, 0x78, - 0x65, 0x2F, 0x8A, 0x05, 0x21, 0x0F, 0xE1, 0x24, 0x12, 0xF0, 0x82, 0x45, 0x35, 0x93, 0xDA, 0x8E, - 0x96, 0x8F, 0xDB, 0xBD, 0x36, 0xD0, 0xCE, 0x94, 0x13, 0x5C, 0xD2, 0xF1, 0x40, 0x46, 0x83, 0x38, - 0x66, 0xDD, 0xFD, 0x30, 0xBF, 0x06, 0x8B, 0x62, 0xB3, 0x25, 0xE2, 0x98, 0x22, 0x88, 0x91, 0x10, - 0x7E, 0x6E, 0x48, 0xC3, 0xA3, 0xB6, 0x1E, 0x42, 0x3A, 0x6B, 0x28, 0x54, 0xFA, 0x85, 0x3D, 0xBA, - 0x2B, 0x79, 0x0A, 0x15, 0x9B, 0x9F, 0x5E, 0xCA, 0x4E, 0xD4, 0xAC, 0xE5, 0xF3, 0x73, 0xA7, 0x57, - 0xAF, 0x58, 0xA8, 0x50, 0xF4, 0xEA, 0xD6, 0x74, 0x4F, 0xAE, 0xE9, 0xD5, 0xE7, 0xE6, 0xAD, 0xE8, - 0x2C, 0xD7, 0x75, 0x7A, 0xEB, 0x16, 0x0B, 0xF5, 0x59, 0xCB, 0x5F, 0xB0, 0x9C, 0xA9, 0x51, 0xA0, - 0x7F, 0x0C, 0xF6, 0x6F, 0x17, 0xC4, 0x49, 0xEC, 0xD8, 0x43, 0x1F, 0x2D, 0xA4, 0x76, 0x7B, 0xB7, - 0xCC, 0xBB, 0x3E, 0x5A, 0xFB, 0x60, 0xB1, 0x86, 0x3B, 0x52, 0xA1, 0x6C, 0xAA, 0x55, 0x29, 0x9D, - 0x97, 0xB2, 0x87, 0x90, 0x61, 0xBE, 0xDC, 0xFC, 0xBC, 0x95, 0xCF, 0xCD, 0x37, 0x3F, 0x5B, 0xD1, - 0x53, 0x39, 0x84, 0x3C, 0x41, 0xA2, 0x6D, 0x47, 0x14, 0x2A, 0x9E, 0x5D, 0x56, 0xF2, 0xD3, 0xAB, - 0x44, 0x11, 0x92, 0xD9, 0x23, 0x20, 0x2E, 0x89, 0xB4, 0x7C, 0xB8, 0x26, 0x77, 0x99, 0xE3, 0xA5, - 0x67, 0x4A, 0xED, 0xDE, 0xC5, 0x31, 0xFE, 0x18, 0x0D, 0x63, 0x8C, 0x80, 0xC0, 0xF7, 0x70, 0x07}; - -local MIXTABLE = { - 0x02, 0x03, 0x01, 0x01, - 0x01, 0x02, 0x03, 0x01, - 0x01, 0x01, 0x02, 0x03, - 0x03, 0x01, 0x01, 0x02}; - -local IMIXTABLE = { - 0x0E, 0x0B, 0x0D, 0x09, - 0x09, 0x0E, 0x0B, 0x0D, - 0x0D, 0x09, 0x0E, 0x0B, - 0x0B, 0x0D, 0x09, 0x0E}; - -local RCON = { -[0] = 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, -0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, -0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, -0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, -0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, -0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, -0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, -0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, -0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, -0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, -0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, -0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, -0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, -0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, -0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, -0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d}; - - -local GMUL = function(A, B) - if(A == 0x01) then return B; end - if(B == 0x01) then return A; end - if(A == 0x00) then return 0; end - if(B == 0x00) then return 0; end - - local LA = LTABLE[A]; - local LB = LTABLE[B]; - - local sum = LA + LB; - if (sum > 0xFF) then sum = sum - 0xFF; end - - return ETABLE[sum]; -end - -local byteSub = Array.substitute; - -local shiftRow = Array.permute; - -local mixCol = function(i, mix) - local out = {}; - - local a, b, c, d; - - a = GMUL(i[ 1], mix[ 1]); - b = GMUL(i[ 2], mix[ 2]); - c = GMUL(i[ 3], mix[ 3]); - d = GMUL(i[ 4], mix[ 4]); - out[ 1] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 1], mix[ 5]); - b = GMUL(i[ 2], mix[ 6]); - c = GMUL(i[ 3], mix[ 7]); - d = GMUL(i[ 4], mix[ 8]); - out[ 2] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 1], mix[ 9]); - b = GMUL(i[ 2], mix[10]); - c = GMUL(i[ 3], mix[11]); - d = GMUL(i[ 4], mix[12]); - out[ 3] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 1], mix[13]); - b = GMUL(i[ 2], mix[14]); - c = GMUL(i[ 3], mix[15]); - d = GMUL(i[ 4], mix[16]); - out[ 4] = XOR(XOR(a, b), XOR(c, d)); - - - a = GMUL(i[ 5], mix[ 1]); - b = GMUL(i[ 6], mix[ 2]); - c = GMUL(i[ 7], mix[ 3]); - d = GMUL(i[ 8], mix[ 4]); - out[ 5] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 5], mix[ 5]); - b = GMUL(i[ 6], mix[ 6]); - c = GMUL(i[ 7], mix[ 7]); - d = GMUL(i[ 8], mix[ 8]); - out[ 6] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 5], mix[ 9]); - b = GMUL(i[ 6], mix[10]); - c = GMUL(i[ 7], mix[11]); - d = GMUL(i[ 8], mix[12]); - out[ 7] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 5], mix[13]); - b = GMUL(i[ 6], mix[14]); - c = GMUL(i[ 7], mix[15]); - d = GMUL(i[ 8], mix[16]); - out[ 8] = XOR(XOR(a, b), XOR(c, d)); - - - a = GMUL(i[ 9], mix[ 1]); - b = GMUL(i[10], mix[ 2]); - c = GMUL(i[11], mix[ 3]); - d = GMUL(i[12], mix[ 4]); - out[ 9] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 9], mix[ 5]); - b = GMUL(i[10], mix[ 6]); - c = GMUL(i[11], mix[ 7]); - d = GMUL(i[12], mix[ 8]); - out[10] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 9], mix[ 9]); - b = GMUL(i[10], mix[10]); - c = GMUL(i[11], mix[11]); - d = GMUL(i[12], mix[12]); - out[11] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[ 9], mix[13]); - b = GMUL(i[10], mix[14]); - c = GMUL(i[11], mix[15]); - d = GMUL(i[12], mix[16]); - out[12] = XOR(XOR(a, b), XOR(c, d)); - - - a = GMUL(i[13], mix[ 1]); - b = GMUL(i[14], mix[ 2]); - c = GMUL(i[15], mix[ 3]); - d = GMUL(i[16], mix[ 4]); - out[13] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[13], mix[ 5]); - b = GMUL(i[14], mix[ 6]); - c = GMUL(i[15], mix[ 7]); - d = GMUL(i[16], mix[ 8]); - out[14] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[13], mix[ 9]); - b = GMUL(i[14], mix[10]); - c = GMUL(i[15], mix[11]); - d = GMUL(i[16], mix[12]); - out[15] = XOR(XOR(a, b), XOR(c, d)); - a = GMUL(i[13], mix[13]); - b = GMUL(i[14], mix[14]); - c = GMUL(i[15], mix[15]); - d = GMUL(i[16], mix[16]); - out[16] = XOR(XOR(a, b), XOR(c, d)); - - return out; -end - -local keyRound = function(key, round) - local i = (round - 1) * 32; - local out = key; - - out[33 + i] = XOR(key[ 1 + i], XOR(SBOX[key[30 + i]], RCON[round])); - out[34 + i] = XOR(key[ 2 + i], SBOX[key[31 + i]]); - out[35 + i] = XOR(key[ 3 + i], SBOX[key[32 + i]]); - out[36 + i] = XOR(key[ 4 + i], SBOX[key[29 + i]]); - - out[37 + i] = XOR(out[33 + i], key[ 5 + i]); - out[38 + i] = XOR(out[34 + i], key[ 6 + i]); - out[39 + i] = XOR(out[35 + i], key[ 7 + i]); - out[40 + i] = XOR(out[36 + i], key[ 8 + i]); - - out[41 + i] = XOR(out[37 + i], key[ 9 + i]); - out[42 + i] = XOR(out[38 + i], key[10 + i]); - out[43 + i] = XOR(out[39 + i], key[11 + i]); - out[44 + i] = XOR(out[40 + i], key[12 + i]); - - out[45 + i] = XOR(out[41 + i], key[13 + i]); - out[46 + i] = XOR(out[42 + i], key[14 + i]); - out[47 + i] = XOR(out[43 + i], key[15 + i]); - out[48 + i] = XOR(out[44 + i], key[16 + i]); - - - out[49 + i] = XOR(SBOX[out[45 + i]], key[17 + i]); - out[50 + i] = XOR(SBOX[out[46 + i]], key[18 + i]); - out[51 + i] = XOR(SBOX[out[47 + i]], key[19 + i]); - out[52 + i] = XOR(SBOX[out[48 + i]], key[20 + i]); - - out[53 + i] = XOR(out[49 + i], key[21 + i]); - out[54 + i] = XOR(out[50 + i], key[22 + i]); - out[55 + i] = XOR(out[51 + i], key[23 + i]); - out[56 + i] = XOR(out[52 + i], key[24 + i]); - - out[57 + i] = XOR(out[53 + i], key[25 + i]); - out[58 + i] = XOR(out[54 + i], key[26 + i]); - out[59 + i] = XOR(out[55 + i], key[27 + i]); - out[60 + i] = XOR(out[56 + i], key[28 + i]); - - out[61 + i] = XOR(out[57 + i], key[29 + i]); - out[62 + i] = XOR(out[58 + i], key[30 + i]); - out[63 + i] = XOR(out[59 + i], key[31 + i]); - out[64 + i] = XOR(out[60 + i], key[32 + i]); - - return out; -end - -local keyExpand = function(key) - local bytes = Array.copy(key); - - for i = 1, 7 do - keyRound(bytes, i); - end - - local keys = {}; - - keys[ 1] = Array.slice(bytes, 1, 16); - keys[ 2] = Array.slice(bytes, 17, 32); - keys[ 3] = Array.slice(bytes, 33, 48); - keys[ 4] = Array.slice(bytes, 49, 64); - keys[ 5] = Array.slice(bytes, 65, 80); - keys[ 6] = Array.slice(bytes, 81, 96); - keys[ 7] = Array.slice(bytes, 97, 112); - keys[ 8] = Array.slice(bytes, 113, 128); - keys[ 9] = Array.slice(bytes, 129, 144); - keys[10] = Array.slice(bytes, 145, 160); - keys[11] = Array.slice(bytes, 161, 176); - keys[12] = Array.slice(bytes, 177, 192); - keys[13] = Array.slice(bytes, 193, 208); - keys[14] = Array.slice(bytes, 209, 224); - keys[15] = Array.slice(bytes, 225, 240); - - return keys; - -end - -local addKey = Array.XOR; - - - -local AES = {}; - -AES.blockSize = 16; - -AES.encrypt = function(key, block) - - local keySchedule = keyExpand(key); - - --round 0 - block = addKey(block, keySchedule[1]); - - --round 1 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[2]); - - --round 2 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[3]); - - --round 3 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[4]); - - --round 4 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[5]); - - --round 5 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[6]); - - --round 6 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[7]); - - --round 7 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[8]); - - --round 8 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[9]); - - --round 9 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[10]); - - --round 10 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[11]); - - --round 11 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[12]); - - --round 12 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[13]); - - --round 13 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = mixCol(block, MIXTABLE); - block = addKey(block, keySchedule[14]); - - --round 14 - block = byteSub(block, SBOX); - block = shiftRow(block, ROW_SHIFT); - block = addKey(block, keySchedule[15]); - - return block; - -end - -AES.decrypt = function(key, block) - - local keySchedule = keyExpand(key); - - --round 0 - block = addKey(block, keySchedule[15]); - - --round 1 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[14]); - block = mixCol(block, IMIXTABLE); - - --round 2 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[13]); - block = mixCol(block, IMIXTABLE); - - --round 3 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[12]); - block = mixCol(block, IMIXTABLE); - - --round 4 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[11]); - block = mixCol(block, IMIXTABLE); - - --round 5 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[10]); - block = mixCol(block, IMIXTABLE); - - --round 6 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[9]); - block = mixCol(block, IMIXTABLE); - - --round 7 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[8]); - block = mixCol(block, IMIXTABLE); - - --round 8 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[7]); - block = mixCol(block, IMIXTABLE); - - --round 9 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[6]); - block = mixCol(block, IMIXTABLE); - - --round 10 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[5]); - block = mixCol(block, IMIXTABLE); - - --round 11 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[4]); - block = mixCol(block, IMIXTABLE); - - --round 12 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[3]); - block = mixCol(block, IMIXTABLE); - - --round 13 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[2]); - block = mixCol(block, IMIXTABLE); - - --round 14 - block = shiftRow(block, IROW_SHIFT); - block = byteSub(block, ISBOX); - block = addKey(block, keySchedule[1]); - - return block; -end - -return AES; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/init.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/init.lua deleted file mode 100644 index 72e8e8a..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/init.lua +++ /dev/null @@ -1,14 +0,0 @@ -local issac = require(".crypto.cipher.issac") -local morus = require(".crypto.cipher.morus") -local aes = require(".crypto.cipher.aes") -local norx = require(".crypto.cipher.norx") - -local cipher = { - _version = "0.0.1", - issac = issac, - morus = morus, - aes = aes, - norx = norx -}; - -return cipher \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/issac.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/issac.lua deleted file mode 100644 index 4ec39fc..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/issac.lua +++ /dev/null @@ -1,204 +0,0 @@ -local Hex = require(".crypto.util.hex"); - --- External Results -local randRsl = {}; -local randCnt = 0; - --- Internal State -local mm = {}; -local aa,bb,cc = 0,0,0; - --- Cap to maintain 32 bit maths -local cap = 0x100000000; - --- CipherMode -local ENCRYPT = 1; -local DECRYPT = 2; - -local function isaac() - cc = ( cc + 1 ) % cap; -- cc just gets incremented once per 256 results - bb = ( bb + cc ) % cap; -- then combined with bb - - for i = 0,255 do - local x = mm[i]; - local y; - local imod = i % 4; - if imod == 0 then aa = aa ~ (aa << 13); - elseif imod == 1 then aa = aa ~ (aa >> 6); - elseif imod == 2 then aa = aa ~ (aa << 2); - elseif imod == 3 then aa = aa ~ (aa >> 16); - end - aa = ( mm[(i+128)%256] + aa ) % cap; - y = ( mm[(x>>2) % 256] + aa + bb ) % cap; - mm[i] = y; - bb = ( mm[(y>>10)%256] + x ) % cap; - randRsl[i] = bb; - end - - randCnt = 0; -- Prepare to use the first set of results. - -end - -local function mix(a) - a[0] = ( a[0] ~ ( a[1] << 11 ) ) % cap; a[3] = ( a[3] + a[0] ) % cap; a[1] = ( a[1] + a[2] ) % cap; - a[1] = ( a[1] ~ ( a[2] >> 2 ) ) % cap; a[4] = ( a[4] + a[1] ) % cap; a[2] = ( a[2] + a[3] ) % cap; - a[2] = ( a[2] ~ ( a[3] << 8 ) ) % cap; a[5] = ( a[5] + a[2] ) % cap; a[3] = ( a[3] + a[4] ) % cap; - a[3] = ( a[3] ~ ( a[4] >> 16 ) ) % cap; a[6] = ( a[6] + a[3] ) % cap; a[4] = ( a[4] + a[5] ) % cap; - a[4] = ( a[4] ~ ( a[5] << 10 ) ) % cap; a[7] = ( a[7] + a[4] ) % cap; a[5] = ( a[5] + a[6] ) % cap; - a[5] = ( a[5] ~ ( a[6] >> 4 ) ) % cap; a[0] = ( a[0] + a[5] ) % cap; a[6] = ( a[6] + a[7] ) % cap; - a[6] = ( a[6] ~ ( a[7] << 8 ) ) % cap; a[1] = ( a[1] + a[6] ) % cap; a[7] = ( a[7] + a[0] ) % cap; - a[7] = ( a[7] ~ ( a[0] >> 9 ) ) % cap; a[2] = ( a[2] + a[7] ) % cap; a[0] = ( a[0] + a[1] ) % cap; -end - -local function randInit(flag) - - -- The golden ratio in 32 bit - -- math.floor((((math.sqrt(5)+1)/2)%1)*2^32) == 2654435769 == 0x9e3779b9 - local a = { [0] = 0x9e3779b9, 0x9e3779b9, 0x9e3779b9, 0x9e3779b9, 0x9e3779b9, 0x9e3779b9, 0x9e3779b9, 0x9e3779b9, }; - - aa,bb,cc = 0,0,0; - - for i = 1,4 do mix(a) end -- Scramble it. - - for i = 0,255,8 do -- Fill in mm[] with messy stuff. - if flag then -- Use all the information in the seed. - for j = 0,7 do - a[j] = ( a[j] + randRsl[i+j] ) % cap; - end - end - mix(a); - for j = 0,7 do - mm[i+j] = a[j]; - end - end - - if flag then - -- Do a second pass to make all of the seed affect all of mm. - for i = 0,255,8 do - for j = 0,7 do - a[j] = ( a[j] + mm[i+j] ) % cap; - end - mix(a); - for j = 0,7 do - mm[i+j] = a[j]; - end - end - end - - isaac(); -- Fill in the first set of results. - randCnt = 0; -- Prepare to use the first set of results. - -end - ---- Seeds the ISAAC random number generator with the given seed. ---- @param seed string - The seed to use for the random number generator. ---- @param flag? boolean - Whether to use all the information in the seed. Defaults to true. -local function seedIsaac(seed, flag) - local seedLength = #seed; - for i = 0,255 do mm[i] = 0; end - for i = 0,255 do randRsl[i] = seed:byte(i+1,i+1) or 0; end - randInit(flag); -end - ---- Retrieves a random number from the ISAAC random number generator ---- @return number: The random number -local function getRandom() - local result = randRsl[randCnt]; - randCnt = randCnt + 1; - if randCnt > 255 then - isaac(); - randCnt = 0; - end - return result; -end - ---- Get a random 32-bit value within the specified range. ---- @param min? number (optional) - The minimum value of the range. Defaults to 0. ---- @param max? number (optional) - The maximum value of the range. Defaults to 2^31-1. ---- @param seed? string (optional) - The seed to use for the random number generator. ---- @return number: The random 32-bit value within the specified range. -local function random(min, max, seed) - local min = min or 0; - local max = max or 2^31-1; - if seed then - seedIsaac(seed, true); - else - seedIsaac(tostring(math.random(2^31-1)), false); - end - return (getRandom() % (max - min + 1)) + min; -end - - ---- Get a random character in printable ASCII range. ---- @return number: The random character [32, 126]. -local function getRandomChar() - return getRandom() % 95 + 32; -end - --- Caesar-shift a character places: Generalized Vigenere -local function caesar(m, ch, shift, modulo, start) - local n - local si = 1 - if m == DECRYPT then shift = shift*-1 ; end - n = (ch - start) + shift; - if n < 0 then si,n = -1,n*-1 ; end - n = ( n % modulo ) * si; - if n < 0 then n = n + modulo ; end - return start + n; -end - ---- Encrypts a message using the ISSAC cipher algorithm. ---- @param msg string - The message to be encrypted. ---- @param key string - The key used for encryption. ---- @returns table - A table containing the encrypted message in bytes, string, and hex formats. -local function encrypt(msg, key) - seedIsaac(key, true); - local msgLength = #msg; - local destination = {}; - - for i = 1, msgLength do - destination[i] = string.char(caesar(1, msg:byte(i, i), getRandomChar(), 95, 32)); - end - - local encrypted = destination - - local public = {} - public.asBytes = function() - return encrypted - end - - public.asString = function() - return table.concat(encrypted) - end - - public.asHex = function() - return Hex.stringToHex(table.concat(encrypted)) - end - - return public -end - ---- Decrypts an encrypted message using the ISSAC cipher algorithm. ---- @param encrypted string - The encrypted message to be decrypted. ---- @param key string - The key used for encryption. ---- @returns string - The decrypted message. -local function decrypt(encrypted, key) - seedIsaac(key, true); - local msgLength = #encrypted; - local destination = {}; - - for i = 1, msgLength do - destination[i] = string.char(caesar(2, encrypted:byte(i, i), getRandomChar(), 95, 32)); - end - - return table.concat(destination); -end - -return { - seedIsaac = seedIsaac, - getRandomChar = getRandomChar, - random = random, - getRandom = getRandom, - encrypt = encrypt, - decrypt = decrypt -} \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cbc.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cbc.lua deleted file mode 100644 index 2879072..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cbc.lua +++ /dev/null @@ -1,171 +0,0 @@ -local Array = require(".crypto.util.array"); -local Stream = require(".crypto.util.stream"); -local Queue = require(".crypto.util.queue"); - -local CBC = {}; - -CBC.Cipher = function() - - local public = {}; - - local key; - local blockCipher; - local padding; - local inputQueue; - local outputQueue; - local iv; - - public.setKey = function(keyBytes) - key = keyBytes; - return public; - end - - public.setBlockCipher = function(cipher) - blockCipher = cipher; - return public; - end - - public.setPadding = function(paddingMode) - padding = paddingMode; - return public; - end - - public.init = function() - inputQueue = Queue(); - outputQueue = Queue(); - iv = nil; - return public; - end - - public.update = function(messageStream) - local byte = messageStream(); - while (byte ~= nil) do - inputQueue.push(byte); - if(inputQueue.size() >= blockCipher.blockSize) then - local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); - - if(iv == nil) then - iv = block; - else - local out = Array.XOR(iv, block); - out = blockCipher.encrypt(key, out); - Array.writeToQueue(outputQueue, out); - iv = out; - end - end - byte = messageStream(); - end - return public; - end - - public.finish = function() - local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); - public.update(paddingStream); - - return public; - end - - public.getOutputQueue = function() - return outputQueue; - end - - public.asHex = function() - return Stream.toHex(outputQueue.pop); - end - - public.asBytes = function() - return Stream.toArray(outputQueue.pop); - end - - public.asString = function() - return Stream.toString(outputQueue.pop); - end - - return public; - -end - - -CBC.Decipher = function() - - local public = {}; - - local key; - local blockCipher; - local padding; - local inputQueue; - local outputQueue; - local iv; - - public.setKey = function(keyBytes) - key = keyBytes; - return public; - end - - public.setBlockCipher = function(cipher) - blockCipher = cipher; - return public; - end - - public.setPadding = function(paddingMode) - padding = paddingMode; - return public; - end - - public.init = function() - inputQueue = Queue(); - outputQueue = Queue(); - iv = nil; - return public; - end - - public.update = function(messageStream) - local byte = messageStream(); - while (byte ~= nil) do - inputQueue.push(byte); - if(inputQueue.size() >= blockCipher.blockSize) then - local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); - - if(iv == nil) then - iv = block; - else - local out = block; - out = blockCipher.decrypt(key, out); - out = Array.XOR(iv, out); - Array.writeToQueue(outputQueue, out); - iv = block; - end - end - byte = messageStream(); - end - return public; - end - - public.finish = function() - local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); - public.update(paddingStream); - - return public; - end - - public.getOutputQueue = function() - return outputQueue; - end - - public.asHex = function() - return Stream.toHex(outputQueue.pop); - end - - public.asBytes = function() - return Stream.toArray(outputQueue.pop); - end - - public.asString = function() - return Stream.toString(outputQueue.pop); - end - - return public; - -end - -return CBC; diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cfb.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cfb.lua deleted file mode 100644 index 331e915..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/cfb.lua +++ /dev/null @@ -1,171 +0,0 @@ -local Array = require(".crypto.util.array"); -local Stream = require(".crypto.util.stream"); -local Queue = require(".crypto.util.queue"); - -local CFB = {}; - -CFB.Cipher = function() - - local public = {}; - - local key; - local blockCipher; - local padding; - local inputQueue; - local outputQueue; - local iv; - - public.setKey = function(keyBytes) - key = keyBytes; - return public; - end - - public.setBlockCipher = function(cipher) - blockCipher = cipher; - return public; - end - - public.setPadding = function(paddingMode) - padding = paddingMode; - return public; - end - - public.init = function() - inputQueue = Queue(); - outputQueue = Queue(); - iv = nil; - return public; - end - - public.update = function(messageStream) - local byte = messageStream(); - while (byte ~= nil) do - inputQueue.push(byte); - if(inputQueue.size() >= blockCipher.blockSize) then - local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); - - if(iv == nil) then - iv = block; - else - local out = iv; - out = blockCipher.encrypt(key, out); - out = Array.XOR(out, block); - Array.writeToQueue(outputQueue, out); - iv = out; - end - end - byte = messageStream(); - end - return public; - end - - public.finish = function() - local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); - public.update(paddingStream); - - return public; - end - - public.getOutputQueue = function() - return outputQueue; - end - - public.asHex = function() - return Stream.toHex(outputQueue.pop); - end - - public.asBytes = function() - return Stream.toArray(outputQueue.pop); - end - - public.asString = function() - return Stream.toString(outputQueue.pop); - end - - return public; - -end - -CFB.Decipher = function() - - local public = {}; - - local key; - local blockCipher; - local padding; - local inputQueue; - local outputQueue; - local iv; - - public.setKey = function(keyBytes) - key = keyBytes; - return public; - end - - public.setBlockCipher = function(cipher) - blockCipher = cipher; - return public; - end - - public.setPadding = function(paddingMode) - padding = paddingMode; - return public; - end - - public.init = function() - inputQueue = Queue(); - outputQueue = Queue(); - iv = nil; - return public; - end - - public.update = function(messageStream) - local byte = messageStream(); - while (byte ~= nil) do - inputQueue.push(byte); - if(inputQueue.size() >= blockCipher.blockSize) then - local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); - - if(iv == nil) then - iv = block; - else - local out = iv; - out = blockCipher.encrypt(key, out); - out = Array.XOR(out, block); - Array.writeToQueue(outputQueue, out); - iv = block; - end - end - byte = messageStream(); - end - return public; - end - - public.finish = function() - local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); - public.update(paddingStream); - - return public; - end - - public.getOutputQueue = function() - return outputQueue; - end - - public.asHex = function() - return Stream.toHex(outputQueue.pop); - end - - public.asBytes = function() - return Stream.toArray(outputQueue.pop); - end - - public.asString = function() - return Stream.toString(outputQueue.pop); - end - - return public; - -end - -return CFB; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ctr.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ctr.lua deleted file mode 100644 index e999bce..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ctr.lua +++ /dev/null @@ -1,252 +0,0 @@ -local Array = require(".crypto.util.array"); -local Stream = require(".crypto.util.stream"); -local Queue = require(".crypto.util.queue"); - -local Bit = require(".crypto.util.bit"); - -local AND = Bit.band; - -local CTR = {}; - -CTR.Cipher = function() - - local public = {}; - - local key; - local blockCipher; - local padding; - local inputQueue; - local outputQueue; - local iv; - - public.setKey = function(keyBytes) - key = keyBytes; - return public; - end - - public.setBlockCipher = function(cipher) - blockCipher = cipher; - return public; - end - - public.setPadding = function(paddingMode) - padding = paddingMode; - return public; - end - - public.init = function() - inputQueue = Queue(); - outputQueue = Queue(); - iv = nil; - return public; - end - - local updateIV = function() - iv[16] = iv[16] + 1; - if iv[16] <= 0xFF then return; end - iv[16] = AND(iv[16], 0xFF); - - iv[15] = iv[15] + 1; - if iv[15] <= 0xFF then return; end - iv[15] = AND(iv[15], 0xFF); - - iv[14] = iv[14] + 1; - if iv[14] <= 0xFF then return; end - iv[14] = AND(iv[14], 0xFF); - - iv[13] = iv[13] + 1; - if iv[13] <= 0xFF then return; end - iv[13] = AND(iv[13], 0xFF); - - iv[12] = iv[12] + 1; - if iv[12] <= 0xFF then return; end - iv[12] = AND(iv[12], 0xFF); - - iv[11] = iv[11] + 1; - if iv[11] <= 0xFF then return; end - iv[11] = AND(iv[11], 0xFF); - - iv[10] = iv[10] + 1; - if iv[10] <= 0xFF then return; end - iv[10] = AND(iv[10], 0xFF); - - iv[9] = iv[9] + 1; - if iv[9] <= 0xFF then return; end - iv[9] = AND(iv[9], 0xFF); - - return; - end - - public.update = function(messageStream) - local byte = messageStream(); - while (byte ~= nil) do - inputQueue.push(byte); - - if(inputQueue.size() >= blockCipher.blockSize) then - local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); - - if(iv == nil) then - iv = block; - else - local out = iv; - out = blockCipher.encrypt(key, out); - - out = Array.XOR(out, block); - Array.writeToQueue(outputQueue, out); - updateIV(); - end - end - byte = messageStream(); - end - return public; - end - - public.finish = function() - local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); - public.update(paddingStream); - - return public; - end - - public.getOutputQueue = function() - return outputQueue; - end - - public.asHex = function() - return Stream.toHex(outputQueue.pop); - end - - public.asBytes = function() - return Stream.toArray(outputQueue.pop); - end - - public.asString = function() - return Stream.toString(outputQueue.pop); - end - - return public; - -end - - -CTR.Decipher = function() - - local public = {}; - - local key; - local blockCipher; - local padding; - local inputQueue; - local outputQueue; - local iv; - - public.setKey = function(keyBytes) - key = keyBytes; - return public; - end - - public.setBlockCipher = function(cipher) - blockCipher = cipher; - return public; - end - - public.setPadding = function(paddingMode) - padding = paddingMode; - return public; - end - - public.init = function() - inputQueue = Queue(); - outputQueue = Queue(); - iv = nil; - return public; - end - - local updateIV = function() - iv[16] = iv[16] + 1; - if iv[16] <= 0xFF then return; end - iv[16] = AND(iv[16], 0xFF); - - iv[15] = iv[15] + 1; - if iv[15] <= 0xFF then return; end - iv[15] = AND(iv[15], 0xFF); - - iv[14] = iv[14] + 1; - if iv[14] <= 0xFF then return; end - iv[14] = AND(iv[14], 0xFF); - - iv[13] = iv[13] + 1; - if iv[13] <= 0xFF then return; end - iv[13] = AND(iv[13], 0xFF); - - iv[12] = iv[12] + 1; - if iv[12] <= 0xFF then return; end - iv[12] = AND(iv[12], 0xFF); - - iv[11] = iv[11] + 1; - if iv[11] <= 0xFF then return; end - iv[11] = AND(iv[11], 0xFF); - - iv[10] = iv[10] + 1; - if iv[10] <= 0xFF then return; end - iv[10] = AND(iv[10], 0xFF); - - iv[9] = iv[9] + 1; - if iv[9] <= 0xFF then return; end - iv[9] = AND(iv[9], 0xFF); - - return; - end - - public.update = function(messageStream) - local byte = messageStream(); - while (byte ~= nil) do - inputQueue.push(byte); - - if(inputQueue.size() >= blockCipher.blockSize) then - local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); - - if(iv == nil) then - iv = block; - else - local out = iv; - out = blockCipher.encrypt(key, out); - - out = Array.XOR(out, block); - Array.writeToQueue(outputQueue, out); - updateIV(); - end - end - byte = messageStream(); - end - return public; - end - - public.finish = function() - local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); - public.update(paddingStream); - - return public; - end - - public.getOutputQueue = function() - return outputQueue; - end - - public.asHex = function() - return Stream.toHex(outputQueue.pop); - end - - public.asBytes = function() - return Stream.toArray(outputQueue.pop); - end - - public.asString = function() - return Stream.toString(outputQueue.pop); - end - - return public; - -end - -return CTR; diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ecb.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ecb.lua deleted file mode 100644 index d0f8618..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ecb.lua +++ /dev/null @@ -1,156 +0,0 @@ -local Array = require(".crypto.util.array"); -local Stream = require(".crypto.util.stream"); -local Queue = require(".crypto.util.queue"); - -local ECB = {}; - -ECB.Cipher = function() - - local public = {}; - - local key; - local blockCipher; - local padding; - local inputQueue; - local outputQueue; - - public.setKey = function(keyBytes) - key = keyBytes; - return public; - end - - public.setBlockCipher = function(cipher) - blockCipher = cipher; - return public; - end - - public.setPadding = function(paddingMode) - padding = paddingMode; - return public; - end - - public.init = function() - inputQueue = Queue(); - outputQueue = Queue(); - return public; - end - - public.update = function(messageStream) - local byte = messageStream(); - while (byte ~= nil) do - inputQueue.push(byte); - if(inputQueue.size() >= blockCipher.blockSize) then - local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); - - block = blockCipher.encrypt(key, block); - - Array.writeToQueue(outputQueue, block); - end - byte = messageStream(); - end - return public; - end - - public.finish = function() - local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); - public.update(paddingStream); - - return public; - end - - public.getOutputQueue = function() - return outputQueue; - end - - public.asHex = function() - return Stream.toHex(outputQueue.pop); - end - - public.asBytes = function() - return Stream.toArray(outputQueue.pop); - end - - public.asString = function() - return Stream.toString(outputQueue.pop); - end - - return public; - -end - -ECB.Decipher = function() - - local public = {}; - - local key; - local blockCipher; - local padding; - local inputQueue; - local outputQueue; - - public.setKey = function(keyBytes) - key = keyBytes; - return public; - end - - public.setBlockCipher = function(cipher) - blockCipher = cipher; - return public; - end - - public.setPadding = function(paddingMode) - padding = paddingMode; - return public; - end - - public.init = function() - inputQueue = Queue(); - outputQueue = Queue(); - return public; - end - - public.update = function(messageStream) - local byte = messageStream(); - while (byte ~= nil) do - inputQueue.push(byte); - if(inputQueue.size() >= blockCipher.blockSize) then - local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); - - block = blockCipher.decrypt(key, block); - - Array.writeToQueue(outputQueue, block); - end - byte = messageStream(); - end - return public; - end - - public.finish = function() - local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); - public.update(paddingStream); - - return public; - end - - public.getOutputQueue = function() - return outputQueue; - end - - public.asHex = function() - return Stream.toHex(outputQueue.pop); - end - - public.asBytes = function() - return Stream.toArray(outputQueue.pop); - end - - public.asString = function() - return Stream.toString(outputQueue.pop); - end - - return public; - -end - - -return ECB; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ofb.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ofb.lua deleted file mode 100644 index adaaed1..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/mode/ofb.lua +++ /dev/null @@ -1,172 +0,0 @@ -local Array = require(".crypto.util.array"); -local Stream = require(".crypto.util.stream"); -local Queue = require(".crypto.util.queue"); - -local OFB = {}; - -OFB.Cipher = function() - - local public = {}; - - local key; - local blockCipher; - local padding; - local inputQueue; - local outputQueue; - local iv; - - public.setKey = function(keyBytes) - key = keyBytes; - return public; - end - - public.setBlockCipher = function(cipher) - blockCipher = cipher; - return public; - end - - public.setPadding = function(paddingMode) - padding = paddingMode; - return public; - end - - public.init = function() - inputQueue = Queue(); - outputQueue = Queue(); - iv = nil; - return public; - end - - public.update = function(messageStream) - local byte = messageStream(); - while (byte ~= nil) do - inputQueue.push(byte); - if(inputQueue.size() >= blockCipher.blockSize) then - local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); - - if(iv == nil) then - iv = block; - else - local out = iv; - out = blockCipher.encrypt(key, out); - iv = out; - out = Array.XOR(out, block); - Array.writeToQueue(outputQueue, out); - end - end - byte = messageStream(); - end - return public; - end - - public.finish = function() - local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); - public.update(paddingStream); - - return public; - end - - public.getOutputQueue = function() - return outputQueue; - end - - public.asHex = function() - return Stream.toHex(outputQueue.pop); - end - - public.asBytes = function() - return Stream.toArray(outputQueue.pop); - end - - public.asString = function() - return Stream.toString(outputQueue.pop); - end - - return public; - -end - -OFB.Decipher = function() - - local public = {}; - - local key; - local blockCipher; - local padding; - local inputQueue; - local outputQueue; - local iv; - - public.setKey = function(keyBytes) - key = keyBytes; - return public; - end - - public.setBlockCipher = function(cipher) - blockCipher = cipher; - return public; - end - - public.setPadding = function(paddingMode) - padding = paddingMode; - return public; - end - - public.init = function() - inputQueue = Queue(); - outputQueue = Queue(); - iv = nil; - return public; - end - - public.update = function(messageStream) - local byte = messageStream(); - while (byte ~= nil) do - inputQueue.push(byte); - if(inputQueue.size() >= blockCipher.blockSize) then - local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); - - if(iv == nil) then - iv = block; - else - local out = iv; - out = blockCipher.encrypt(key, out); - iv = out; - out = Array.XOR(out, block); - Array.writeToQueue(outputQueue, out); - end - end - byte = messageStream(); - end - return public; - end - - public.finish = function() - local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); - public.update(paddingStream); - - return public; - end - - public.getOutputQueue = function() - return outputQueue; - end - - public.asHex = function() - return Stream.toHex(outputQueue.pop); - end - - public.asBytes = function() - return Stream.toArray(outputQueue.pop); - end - - public.asString = function() - return Stream.toString(outputQueue.pop); - end - - return public; - -end - - -return OFB; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/morus.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/morus.lua deleted file mode 100644 index 1263337..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/cipher/morus.lua +++ /dev/null @@ -1,398 +0,0 @@ -local Hex = require(".crypto.util.hex"); - -local function state_update(s, m0, m1, m2, m3) - local s00, s01, s02, s03 = s[1], s[2], s[3], s[4] - local s10, s11, s12, s13 = s[5], s[6], s[7], s[8] - local s20, s21, s22, s23 = s[9], s[10], s[11], s[12] - local s30, s31, s32, s33 = s[13], s[14], s[15], s[16] - local s40, s41, s42, s43 = s[17], s[18], s[19], s[20] - local temp - - s00 = s00 ~ s30 - s01 = s01 ~ s31 - s02 = s02 ~ s32 - s03 = s03 ~ s33 - - temp = s33 - s33 = s32 - s32 = s31 - s31 = s30 - s30 = temp - - s00 = s00 ~ s10 & s20 - s01 = s01 ~ s11 & s21 - s02 = s02 ~ s12 & s22 - s03 = s03 ~ s13 & s23 - - s00 = (s00 << 13) | (s00 >> (64-13)) --n1 - s01 = (s01 << 13) | (s01 >> (64-13)) --n1 - s02 = (s02 << 13) | (s02 >> (64-13)) --n1 - s03 = (s03 << 13) | (s03 >> (64-13)) --n1 - - - s10 = s10 ~ m0 - s11 = s11 ~ m1 - s12 = s12 ~ m2 - s13 = s13 ~ m3 - - s10 = s10 ~ s40 - s11 = s11 ~ s41 - s12 = s12 ~ s42 - s13 = s13 ~ s43 - - temp = s43 - s43 = s41 - s41 = temp - - temp = s42 - s42 = s40 - s40 = temp - - s10 = s10 ~ (s20 & s30) - s11 = s11 ~ (s21 & s31) - s12 = s12 ~ (s22 & s32) - s13 = s13 ~ (s23 & s33) - - s10 = (s10 << 46) | (s10 >> (64-46)) --n2 - s11 = (s11 << 46) | (s11 >> (64-46)) --n2 - s12 = (s12 << 46) | (s12 >> (64-46)) --n2 - s13 = (s13 << 46) | (s13 >> (64-46)) --n2 - - - s20 = s20 ~ m0 - s21 = s21 ~ m1 - s22 = s22 ~ m2 - s23 = s23 ~ m3 - - s20 = s20 ~ s00 - s21 = s21 ~ s01 - s22 = s22 ~ s02 - s23 = s23 ~ s03 - - temp = s00 - s00 = s01 - s01 = s02 - s02 = s03 - s03 = temp - - s20 = s20 ~ s30 & s40 - s21 = s21 ~ s31 & s41 - s22 = s22 ~ s32 & s42 - s23 = s23 ~ s33 & s43 - - s20 = (s20 << 38) | (s20 >> (64-38)) --n3 - s21 = (s21 << 38) | (s21 >> (64-38)) --n3 - s22 = (s22 << 38) | (s22 >> (64-38)) --n3 - s23 = (s23 << 38) | (s23 >> (64-38)) --n3 - - - s30 = s30 ~ m0 - s31 = s31 ~ m1 - s32 = s32 ~ m2 - s33 = s33 ~ m3 - - s30 = s30 ~ s10 - s31 = s31 ~ s11 - s32 = s32 ~ s12 - s33 = s33 ~ s13 - - temp = s13 - s13 = s11 - s11 = temp - - temp = s12 - s12 = s10 - s10 = temp - - s30 = s30 ~ s40 & s00 - s31 = s31 ~ s41 & s01 - s32 = s32 ~ s42 & s02 - s33 = s33 ~ s43 & s03 - - s30 = (s30 << 7) | (s30 >> (64-7)) --n4 - s31 = (s31 << 7) | (s31 >> (64-7)) --n4 - s32 = (s32 << 7) | (s32 >> (64-7)) --n4 - s33 = (s33 << 7) | (s33 >> (64-7)) --n4 - - - s40 = s40 ~ m0 - s41 = s41 ~ m1 - s42 = s42 ~ m2 - s43 = s43 ~ m3 - - s40 = s40 ~ s20 - s41 = s41 ~ s21 - s42 = s42 ~ s22 - s43 = s43 ~ s23 - - temp = s23 - s23 = s22 - s22 = s21 - s21 = s20 - s20 = temp - - s40 = s40 ~ s00 & s10 - s41 = s41 ~ s01 & s11 - s42 = s42 ~ s02 & s12 - s43 = s43 ~ s03 & s13 - - s40 = (s40 << 4) | (s40 >> (64-4)) --n5 - s41 = (s41 << 4) | (s41 >> (64-4)) --n5 - s42 = (s42 << 4) | (s42 >> (64-4)) --n5 - s43 = (s43 << 4) | (s43 >> (64-4)) --n5 - - -- update the state array - s[1], s[2], s[3], s[4] = s00, s01, s02, s03 - s[5], s[6], s[7], s[8] = s10, s11, s12, s13 - s[9], s[10], s[11], s[12] = s20, s21, s22, s23 - s[13], s[14], s[15], s[16] = s30, s31, s32, s33 - s[17], s[18], s[19], s[20] = s40, s41, s42, s43 - -end--state_update() - -local function enc_aut_step(s, m0, m1, m2, m3) - -- m0 s00 s11 s20 s30 - local c0 = m0 ~ s[1] ~ s[6] ~ (s[9] & s[13]) - -- m1 s01 s12 s21 s31 - local c1 = m1 ~ s[2] ~ s[7] ~ (s[10] & s[14]) - -- m2 s02 s13 s22 s32 - local c2 = m2 ~ s[3] ~ s[8] ~ (s[11] & s[15]) - -- m3 s03 s10 s23 s33 - local c3 = m3 ~ s[4] ~ s[5] ~ (s[12] & s[16]) - state_update(s, m0, m1, m2, m3) - return c0, c1, c2, c3 -end - -local function dec_aut_step(s, c0, c1, c2, c3, blen) - -- mlen is the length of a last partial block - -- mlen is absent/nil for full blocks - -- return the decrypted block - -- - -- m0 s00 s11 s20 s30 - local m0 = c0 ~ s[1] ~ s[6] ~ (s[9] & s[13]) - -- m1 s01 s12 s21 s31 - local m1 = c1 ~ s[2] ~ s[7] ~ (s[10] & s[14]) - -- m2 s02 s13 s22 s32 - local m2 = c2 ~ s[3] ~ s[8] ~ (s[11] & s[15]) - -- m3 s03 s10 s23 s33 - local m3 = c3 ~ s[4] ~ s[5] ~ (s[12] & s[16]) - if blen then - -- partial block => must adjust (m0, ...) before - -- updating the state - local mblk = string.pack(" 0 then ad = e:sub(1, adlen) end - local i = 1 - while i <= adlen - 31 do --process full blocks - m0, m1, m2, m3 = string.unpack("> n) | (x << (64-n)) --INLINED - -- - A = (A ~ B) ~ ((A & B) << 1) -- H(A, B); - D = D ~ A; D = (D >> 8) | (D << (56)) --ROTR64(D, 8) --R0 - C = (C ~ D) ~ ((C & D) << 1) -- H(C, D); - B = B ~ C; B = (B >> 19) | (B << (45)) --ROTR64(B, 19) --R1 - A = (A ~ B) ~ ((A & B) << 1) -- H(A, B); - D = D ~ A; D = (D >> 40) | (D << (24)) --ROTR64(D, 40) --R2 - C = (C ~ D) ~ ((C & D) << 1) -- H(C, D); - B = B ~ C; B = (B >> 63) | (B << (1)) --ROTR64(B, 63) --R3 - s[a], s[b], s[c], s[d] = A, B, C, D -end - -local function F(s) - -- The full round. s is the state: u64[16] - -- - -- beware! in Lua, arrays are 1-based indexed, not 0-indexed as in C - -- Column step - G(s, 1, 5, 9, 13); - G(s, 2, 6, 10, 14); - G(s, 3, 7, 11, 15); - G(s, 4, 8, 12, 16); - -- Diagonal step - G(s, 1, 6, 11, 16); - G(s, 2, 7, 12, 13); - G(s, 3, 8, 9, 14); - G(s, 4, 5, 10, 15); -end - -local function permute(s) - -- the core permutation (four rounds) - for _ = 1, 4 do F(s) end -end - -local function pad(ins) - -- pad string ins to length 96 ("BYTES(NORX_R)") - local out - local inslen = #ins - if inslen == 95 then return ins .. '\x81' end -- last byte is 0x01 | 0x80 - -- here inslen is < 95, so must pad with 96-(inslen+2) zeros - out = ins .. '\x01' .. string.rep('\0', 94-inslen) .. '\x80' - assert(#out == 96) - return out -end - -local function absorb_block(s, ins, ini, tag) - -- the input string is the substring of 'ins' starting at position 'ini' - -- (we cannot use a char* as in C!) - s[16] = s[16] ~ tag - permute(s) - for i = 1, 12 do - s[i] = s[i] ~ string.unpack(" 0 then - while inlen >= 96 do - absorb_block(s, ins, i, tag) - inlen = inlen - 96 - i = i + 96 - end - absorb_lastblock(s, ins:sub(i), tag) - end--if -end - -local function encrypt_data(s, out_table, ins) - local inlen = #ins - local i = 1 - if inlen > 0 then - while inlen >= 96 do - encrypt_block(s, out_table, ins, i) - inlen = inlen - 96 - i = i + 96 - end - encrypt_lastblock(s, out_table, ins:sub(i)) - end -end - -local function decrypt_data(s, out_table, ins) - local inlen = #ins - local i = 1 - if inlen > 0 then - while inlen >= 96 do - decrypt_block(s, out_table, ins, i) - inlen = inlen - 96 - i = i + 96 - end - decrypt_lastblock(s, out_table, ins:sub(i)) - end -end - -local function finalize(s, k) - -- return the authentication tag (32-byte string) - -- - s[16] = s[16] ~ FINAL_TAG - permute(s) - -- - local k1, k2, k3, k4 = string.unpack("= 32) - local out_table = {} - local state = init(key, nonce) - absorb_data(state, header, HEADER_TAG) - local ctag = crypted:sub(#crypted - 32 + 1) - local c = crypted:sub(1, #crypted - 32) - decrypt_data(state, out_table, c) - absorb_data(state, trailer, TRAILER_TAG) - local tag = finalize(state, key) - if not verify_tag(tag, ctag) then return nil, "auth failure" end - local plain = table.concat(out_table) - return plain -end - -return { - encrypt = aead_encrypt, - decrypt = aead_decrypt, - -- - key_size = 32, - nonce_size = 32, - variant = "NORX 64-4-1", -} diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/blake2b.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/blake2b.lua deleted file mode 100644 index 0b68940..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/blake2b.lua +++ /dev/null @@ -1,190 +0,0 @@ -local Hex = require(".crypto.util.hex"); - - -local function ROTR64(x, n) - return (x >> n) | (x << (64-n)) -end - --- G Mixing function. - -local function G(v, a, b, c, d, x, y) - v[a] = v[a] + v[b] + x - v[d] = ROTR64(v[d] ~ v[a], 32) - v[c] = v[c] + v[d] - v[b] = ROTR64(v[b] ~ v[c], 24) - v[a] = v[a] + v[b] + y - v[d] = ROTR64(v[d] ~ v[a], 16) - v[c] = v[c] + v[d] - v[b] = ROTR64(v[b] ~ v[c], 63) -end - --- Initialization Vector. -local iv = { - 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, - 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, - 0x510e527fade682d1, 0x9b05688c2b3e6c1f, - 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179 -} - -local sigma = { - -- array index start at 1 in Lua, - -- => all the permutation values are incremented by one - { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, - { 15, 11, 5, 9, 10, 16, 14, 7, 2, 13, 1, 3, 12, 8, 6, 4 }, - { 12, 9, 13, 1, 6, 3, 16, 14, 11, 15, 4, 7, 8, 2, 10, 5 }, - { 8, 10, 4, 2, 14, 13, 12, 15, 3, 7, 6, 11, 5, 1, 16, 9 }, - { 10, 1, 6, 8, 3, 5, 11, 16, 15, 2, 12, 13, 7, 9, 4, 14 }, - { 3, 13, 7, 11, 1, 12, 9, 4, 5, 14, 8, 6, 16, 15, 2, 10 }, - { 13, 6, 2, 16, 15, 14, 5, 11, 1, 8, 7, 4, 10, 3, 9, 12 }, - { 14, 12, 8, 15, 13, 2, 4, 10, 6, 1, 16, 5, 9, 7, 3, 11 }, - { 7, 16, 15, 10, 12, 4, 1, 9, 13, 3, 14, 8, 2, 5, 11, 6 }, - { 11, 3, 9, 5, 8, 7, 2, 6, 16, 12, 10, 15, 4, 13, 14, 1 }, - { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, - { 15, 11, 5, 9, 10, 16, 14, 7, 2, 13, 1, 3, 12, 8, 6, 4 } -} - - -local function compress(ctx, last) - -- Compression function. "last" flag indicates last block. - local v, m = {}, {} -- both v and m are u64[16] - for i = 1, 8 do - v[i] = ctx.h[i] - v[i+8] = iv[i] - end - v[13] = v[13] ~ ctx.t[1] -- low 64 bits of offset - v[14] = v[14] ~ ctx.t[2] -- high 64 bits - if last then v[15] = ~v[15] end - for i = 0, 15 do -- get little-endian words - m[i+1] = string.unpack(" 64 or (key and #key > 64) then - return nil, "illegal parameters" - end - local ctx = {h={}, t={}, c=1, outlen=outlen} -- the blake2 context - -- note: ctx.c is the index of 1st byte free in input buffer (ctx.b) - -- it is not used in this implementation - for i = 1, 8 do ctx.h[i] = iv[i] end -- state, "param block" - ctx.h[1] = ctx.h[1] ~ 0x01010000 ~ (keylen << 8) ~ outlen - ctx.t[1] = 0 --input count low word - ctx.t[2] = 0 --input count high word - -- zero input block - ctx.b = "" - if keylen > 0 then - update(ctx, key) - -- ctx.c = 128 -- pad b with zero bytes - ctx.b = ctx.b .. string.rep('\0', 128 - #ctx.b) - assert(#ctx.b == 128) - end - return ctx -end - -update = function(ctx, data) - -- buffer mgt cannot be done the C way.. - local bln, rln, iln - local i = 1 -- index of 1st byte to process in data - while true do - bln = #ctx.b -- current number of bytes in the input buffer - assert(bln <= 128) - if bln == 128 then --ctx.b is full; process it. - -- add counters - ctx.t[1] = ctx.t[1] + 128 - -- warning: this is a signed 64bit addition - -- here it is assumed that the total input is less - -- than 2^63 bytes (this should be enough for a - -- pure Lua implementation!) => ctx.t[1] overflow is ignored. - compress(ctx, false) -- false means not last - ctx.b = "" -- empty buffer - else -- ctx.b is not full; append more bytes from data - rln = 128 - bln -- remaining space (in bytes) in ctx.b - iln = #data - i + 1 -- number of bytes yet to process in data - if iln < rln then - ctx.b = ctx.b .. data:sub(i, i + iln -1) - -- here, all data bytes have been processed or put in - -- buffer and buffer is not full. we are done. - break - else - ctx.b = ctx.b .. data:sub(i, i + rln -1) - i = i + rln - end - end - end -end - -local function final(ctx) - -- finalize the hash and return the digest as a string - -- - local bln = #ctx.b - -- add number of remaining bytes in buffer (ignore carry overflow) - ctx.t[1] = ctx.t[1] + bln - -- pad the buffer with zero bytes - local rln = 128 - bln -- remaining space (in bytes) in ctx.b - ctx.b = ctx.b .. string.rep('\0', rln) - compress(ctx, true) -- true means final block - -- extract the digest (outlen bytes long) - local outtbl = {} - for i = 0, ctx.outlen - 1 do - outtbl[i+1] = string.char( - (ctx.h[(i >> 3) + 1] >> (8 * (i & 7))) & 0xff) - end - local dig = table.concat(outtbl) - return outtbl -end - ---- Calculates the BLAKE2b hash of the given data. ---- @param data string - The input data to be hashed. ---- @param outlen? number (optional) - The desired length of the hash output. Defaults to 64. ---- @param key? string (optional) - The key to be used for the hash calculation. Defaults to an empty string. ---- @returns table - A table containing the hash in bytes, string, and hex formats. -local function black2b(data, outlen, key) - local ctx, msg = init(outlen, key) - if not ctx then return ctx, msg end - update(ctx, data) - local bytes = final(ctx) - local hash = table.concat(bytes) - - local public = {} - - public.asBytes = function() - return bytes - end - - public.asString = function() - return hash - end - - public.asHex = function() - return Hex.stringToHex(hash) - end - - return public -end - -return black2b diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/init.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/init.lua deleted file mode 100644 index 0646c29..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/init.lua +++ /dev/null @@ -1,29 +0,0 @@ -local MD2 = require(".crypto.digest.md2") -local MD4 = require(".crypto.digest.md4") -local MD5 = require(".crypto.digest.md5") -local SHA1 = require(".crypto.digest.sha1") -local SHA2_256 = require(".crypto.digest.sha2_256") -local SHA2_512 = require(".crypto.digest.sha2_512") -local SHA3 = require(".crypto.digest.sha3") -local Blake2b = require(".crypto.digest.blake2b") - - -local digest = { - _version = "0.0.1", - md2 = MD2, - md4 = MD4, - md5 = MD5, - sha1 = SHA1.sha1, - sha2_256 = SHA2_256.sha2_256, - sha2_512 = SHA2_512, - sha3_256 = SHA3.sha3_256, - sha3_512 = SHA3.sha3_512, - keccak256 = SHA3.keccak256, - keccak512 = SHA3.keccak512, - blake2b = Blake2b -} - - - - -return digest diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md2.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md2.lua deleted file mode 100644 index 87fede0..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md2.lua +++ /dev/null @@ -1,112 +0,0 @@ -local Bit = require(".crypto.util.bit"); -local Queue = require(".crypto.util.queue"); - -local SUBST = { - 0x29, 0x2E, 0x43, 0xC9, 0xA2, 0xD8, 0x7C, 0x01, 0x3D, 0x36, 0x54, 0xA1, 0xEC, 0xF0, 0x06, 0x13, - 0x62, 0xA7, 0x05, 0xF3, 0xC0, 0xC7, 0x73, 0x8C, 0x98, 0x93, 0x2B, 0xD9, 0xBC, 0x4C, 0x82, 0xCA, - 0x1E, 0x9B, 0x57, 0x3C, 0xFD, 0xD4, 0xE0, 0x16, 0x67, 0x42, 0x6F, 0x18, 0x8A, 0x17, 0xE5, 0x12, - 0xBE, 0x4E, 0xC4, 0xD6, 0xDA, 0x9E, 0xDE, 0x49, 0xA0, 0xFB, 0xF5, 0x8E, 0xBB, 0x2F, 0xEE, 0x7A, - 0xA9, 0x68, 0x79, 0x91, 0x15, 0xB2, 0x07, 0x3F, 0x94, 0xC2, 0x10, 0x89, 0x0B, 0x22, 0x5F, 0x21, - 0x80, 0x7F, 0x5D, 0x9A, 0x5A, 0x90, 0x32, 0x27, 0x35, 0x3E, 0xCC, 0xE7, 0xBF, 0xF7, 0x97, 0x03, - 0xFF, 0x19, 0x30, 0xB3, 0x48, 0xA5, 0xB5, 0xD1, 0xD7, 0x5E, 0x92, 0x2A, 0xAC, 0x56, 0xAA, 0xC6, - 0x4F, 0xB8, 0x38, 0xD2, 0x96, 0xA4, 0x7D, 0xB6, 0x76, 0xFC, 0x6B, 0xE2, 0x9C, 0x74, 0x04, 0xF1, - 0x45, 0x9D, 0x70, 0x59, 0x64, 0x71, 0x87, 0x20, 0x86, 0x5B, 0xCF, 0x65, 0xE6, 0x2D, 0xA8, 0x02, - 0x1B, 0x60, 0x25, 0xAD, 0xAE, 0xB0, 0xB9, 0xF6, 0x1C, 0x46, 0x61, 0x69, 0x34, 0x40, 0x7E, 0x0F, - 0x55, 0x47, 0xA3, 0x23, 0xDD, 0x51, 0xAF, 0x3A, 0xC3, 0x5C, 0xF9, 0xCE, 0xBA, 0xC5, 0xEA, 0x26, - 0x2C, 0x53, 0x0D, 0x6E, 0x85, 0x28, 0x84, 0x09, 0xD3, 0xDF, 0xCD, 0xF4, 0x41, 0x81, 0x4D, 0x52, - 0x6A, 0xDC, 0x37, 0xC8, 0x6C, 0xC1, 0xAB, 0xFA, 0x24, 0xE1, 0x7B, 0x08, 0x0C, 0xBD, 0xB1, 0x4A, - 0x78, 0x88, 0x95, 0x8B, 0xE3, 0x63, 0xE8, 0x6D, 0xE9, 0xCB, 0xD5, 0xFE, 0x3B, 0x00, 0x1D, 0x39, - 0xF2, 0xEF, 0xB7, 0x0E, 0x66, 0x58, 0xD0, 0xE4, 0xA6, 0x77, 0x72, 0xF8, 0xEB, 0x75, 0x4B, 0x0A, - 0x31, 0x44, 0x50, 0xB4, 0x8F, 0xED, 0x1F, 0x1A, 0xDB, 0x99, 0x8D, 0x33, 0x9F, 0x11, 0x83, 0x14 }; - -local XOR = Bit.bxor; - -local MD2 = function(stream) - local queue = Queue(); - local public = {} - - local X = {}; - for i = 0, 47 do - X[i] = 0x00; - end - - local C = {}; - for i = 0, 15 do - C[i] = 0x00; - end - - local processBlock = function() - local block = {}; - - for i = 0, 15 do - block[i] = queue.pop(); - end - - for i = 0, 15 do - X[i + 16] = block[i]; - X[i + 32] = XOR(X[i], block[i]); --mix - end - - local t; - - --update block - t = 0; - for i = 0, 17 do - for j = 0, 47 do - X[j] = XOR(X[j], SUBST[t + 1]); - t = X[j]; - end - t = (t + i) % 256; - end - - --update checksum - t = C[15]; - for i = 0, 15 do - C[i] = XOR(C[i], SUBST[XOR(block[i], t) + 1]); - t = C[i]; - end - - end - - for b in stream do - queue.push(b); - if(queue.size() >= 16) then processBlock(); end - end - - local i = 16 - queue.size(); - - while queue.size() < 16 do - queue.push(i); - end - - processBlock(); - - queue.push(C[ 0]); queue.push(C[ 1]); queue.push(C[ 2]); queue.push(C[ 3]); - queue.push(C[ 4]); queue.push(C[ 5]); queue.push(C[ 6]); queue.push(C[ 7]); - queue.push(C[ 8]); queue.push(C[ 9]); queue.push(C[10]); queue.push(C[11]); - queue.push(C[12]); queue.push(C[13]); queue.push(C[14]); queue.push(C[15]); - - processBlock(); - - public.asBytes = function() - return {X[ 0], X[ 1], X[ 2], X[ 3], X[ 4], X[ 5], X[ 6], X[ 7], - X[ 8], X[ 9], X[10], X[11], X[12], X[13], X[14], X[15]}; - end - - public.asHex = function() - return string.format("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - X[ 0], X[ 1], X[ 2], X[ 3], X[ 4], X[ 5], X[ 6], X[ 7], - X[ 8], X[ 9], X[10], X[11], X[12], X[13], X[14], X[15]); - end - - public.asString = function() - return string.pack(string.rep('B', 16), - X[ 0], X[ 1], X[ 2], X[ 3], X[ 4], X[ 5], X[ 6], X[ 7], - X[ 8], X[ 9], X[10], X[11], X[12], X[13], X[14], X[15]); - end - - return public; - -end - -return MD2; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md4.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md4.lua deleted file mode 100644 index eee89a7..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md4.lua +++ /dev/null @@ -1,193 +0,0 @@ -local Bit = require(".crypto.util.bit"); -local Queue = require(".crypto.util.queue"); - -local AND = Bit.band; -local OR = Bit.bor; -local NOT = Bit.bnot; -local XOR = Bit.bxor; -local LROT = Bit.lrotate; -local LSHIFT = Bit.lshift; -local RSHIFT = Bit.rshift; - ---MD4 is little-endian -local bytes2word = function(b0, b1, b2, b3) - local i = b3; i = LSHIFT(i, 8); - i = OR(i, b2); i = LSHIFT(i, 8); - i = OR(i, b1); i = LSHIFT(i, 8); - i = OR(i, b0); - return i; -end - -local word2bytes = function(word) - local b0, b1, b2, b3; - b0 = AND(word, 0xFF); word = RSHIFT(word, 8); - b1 = AND(word, 0xFF); word = RSHIFT(word, 8); - b2 = AND(word, 0xFF); word = RSHIFT(word, 8); - b3 = AND(word, 0xFF); - return b0, b1, b2, b3; -end - -local dword2bytes = function(i) - local b4, b5, b6, b7 = word2bytes(math.floor(i / 0x100000000)); - local b0, b1, b2, b3 = word2bytes(i); - return b0, b1, b2, b3, b4, b5, b6, b7; -end - -local F = function(x, y, z) return OR(AND(x, y), AND(NOT(x), z)); end -local G = function(x, y, z) return OR(AND(x, y), OR(AND(x, z), AND(y, z))); end -local H = function(x, y, z) return XOR(x, XOR(y, z)); end - - -local MD4 = function(stream) - - local queue = Queue(); - - local A = 0x67452301; - local B = 0xefcdab89; - local C = 0x98badcfe; - local D = 0x10325476; - local public = {}; - - local processBlock = function() - local a = A; - local b = B; - local c = C; - local d = D; - - local X = {}; - - for i = 0, 15 do - X[i] = bytes2word(queue.pop(), queue.pop(), queue.pop(), queue.pop()); - end - - a = LROT(a + F(b, c, d) + X[0], 3); - d = LROT(d + F(a, b, c) + X[1], 7); - c = LROT(c + F(d, a, b) + X[2], 11); - b = LROT(b + F(c, d, a) + X[3], 19); - - a = LROT(a + F(b, c, d) + X[4], 3); - d = LROT(d + F(a, b, c) + X[5], 7); - c = LROT(c + F(d, a, b) + X[6], 11); - b = LROT(b + F(c, d, a) + X[7], 19); - - a = LROT(a + F(b, c, d) + X[8], 3); - d = LROT(d + F(a, b, c) + X[9], 7); - c = LROT(c + F(d, a, b) + X[10], 11); - b = LROT(b + F(c, d, a) + X[11], 19); - - a = LROT(a + F(b, c, d) + X[12], 3); - d = LROT(d + F(a, b, c) + X[13], 7); - c = LROT(c + F(d, a, b) + X[14], 11); - b = LROT(b + F(c, d, a) + X[15], 19); - - - a = LROT(a + G(b, c, d) + X[0] + 0x5A827999, 3); - d = LROT(d + G(a, b, c) + X[4] + 0x5A827999, 5); - c = LROT(c + G(d, a, b) + X[8] + 0x5A827999, 9); - b = LROT(b + G(c, d, a) + X[12] + 0x5A827999, 13); - - a = LROT(a + G(b, c, d) + X[1] + 0x5A827999, 3); - d = LROT(d + G(a, b, c) + X[5] + 0x5A827999, 5); - c = LROT(c + G(d, a, b) + X[9] + 0x5A827999, 9); - b = LROT(b + G(c, d, a) + X[13] + 0x5A827999, 13); - - a = LROT(a + G(b, c, d) + X[2] + 0x5A827999, 3); - d = LROT(d + G(a, b, c) + X[6] + 0x5A827999, 5); - c = LROT(c + G(d, a, b) + X[10] + 0x5A827999, 9); - b = LROT(b + G(c, d, a) + X[14] + 0x5A827999, 13); - - a = LROT(a + G(b, c, d) + X[3] + 0x5A827999, 3); - d = LROT(d + G(a, b, c) + X[7] + 0x5A827999, 5); - c = LROT(c + G(d, a, b) + X[11] + 0x5A827999, 9); - b = LROT(b + G(c, d, a) + X[15] + 0x5A827999, 13); - - - a = LROT(a + H(b, c, d) + X[0] + 0x6ED9EBA1, 3); - d = LROT(d + H(a, b, c) + X[8] + 0x6ED9EBA1, 9); - c = LROT(c + H(d, a, b) + X[4] + 0x6ED9EBA1, 11); - b = LROT(b + H(c, d, a) + X[12] + 0x6ED9EBA1, 15); - - a = LROT(a + H(b, c, d) + X[2] + 0x6ED9EBA1, 3); - d = LROT(d + H(a, b, c) + X[10] + 0x6ED9EBA1, 9); - c = LROT(c + H(d, a, b) + X[6] + 0x6ED9EBA1, 11); - b = LROT(b + H(c, d, a) + X[14] + 0x6ED9EBA1, 15); - - a = LROT(a + H(b, c, d) + X[1] + 0x6ED9EBA1, 3); - d = LROT(d + H(a, b, c) + X[9] + 0x6ED9EBA1, 9); - c = LROT(c + H(d, a, b) + X[5] + 0x6ED9EBA1, 11); - b = LROT(b + H(c, d, a) + X[13] + 0x6ED9EBA1, 15); - - a = LROT(a + H(b, c, d) + X[3] + 0x6ED9EBA1, 3); - d = LROT(d + H(a, b, c) + X[11] + 0x6ED9EBA1, 9); - c = LROT(c + H(d, a, b) + X[7] + 0x6ED9EBA1, 11); - b = LROT(b + H(c, d, a) + X[15] + 0x6ED9EBA1, 15); - - - A = AND(A + a, 0xFFFFFFFF); - B = AND(B + b, 0xFFFFFFFF); - C = AND(C + c, 0xFFFFFFFF); - D = AND(D + d, 0xFFFFFFFF); - end - - - for s in stream do - queue.push(s); - if (queue.size() >= 64) then processBlock(); end - end - - local bits = queue.getHead() * 8; - - queue.push(0x80); - while ((queue.size() + 7) % 64) < 63 do - queue.push(0x00); - end - - local b0, b1, b2, b3, b4, b5, b6, b7 = dword2bytes(bits); - - queue.push(b0); - queue.push(b1); - queue.push(b2); - queue.push(b3); - queue.push(b4); - queue.push(b5); - queue.push(b6); - queue.push(b7); - - while queue.size() > 0 do - processBlock(); - end - - public.asBytes = function() - local b0, b1, b2, b3 = word2bytes(A); - local b4, b5, b6, b7 = word2bytes(B); - local b8, b9, b10, b11 = word2bytes(C); - local b12, b13, b14, b15 = word2bytes(D); - - return {b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15}; - end - - public.asHex = function() - local b0, b1, b2, b3 = word2bytes(A); - local b4, b5, b6, b7 = word2bytes(B); - local b8, b9, b10, b11 = word2bytes(C); - local b12, b13, b14, b15 = word2bytes(D); - - return string.format("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15); - end - - public.asString = function() - local b0, b1, b2, b3 = word2bytes(A); - local b4, b5, b6, b7 = word2bytes(B); - local b8, b9, b10, b11 = word2bytes(C); - local b12, b13, b14, b15 = word2bytes(D); - - return string.pack(string.rep('B', 16), - b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15); - end - - return public; - -end - -return MD4; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md5.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md5.lua deleted file mode 100644 index be40518..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/md5.lua +++ /dev/null @@ -1,178 +0,0 @@ -local Bit = require(".crypto.util.bit"); -local Queue = require(".crypto.util.queue"); - -local SHIFT = { - 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, - 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, - 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, - 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; - -local CONSTANTS = { - 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, - 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, - 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, - 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, - 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, - 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, - 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, - 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, - 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, - 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, - 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, - 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, - 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, - 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, - 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, - 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; - -local AND = Bit.band; -local OR = Bit.bor; -local NOT = Bit.bnot; -local XOR = Bit.bxor; -local LROT = Bit.lrotate; -local LSHIFT = Bit.lshift; -local RSHIFT = Bit.rshift; - ---MD5 is little-endian -local bytes2word = function(b0, b1, b2, b3) - local i = b3; i = LSHIFT(i, 8); - i = OR(i, b2); i = LSHIFT(i, 8); - i = OR(i, b1); i = LSHIFT(i, 8); - i = OR(i, b0); - return i; -end - -local word2bytes = function(word) - local b0, b1, b2, b3; - b0 = AND(word, 0xFF); word = RSHIFT(word, 8); - b1 = AND(word, 0xFF); word = RSHIFT(word, 8); - b2 = AND(word, 0xFF); word = RSHIFT(word, 8); - b3 = AND(word, 0xFF); - return b0, b1, b2, b3; -end - -local dword2bytes = function(i) - local b4, b5, b6, b7 = word2bytes(math.floor(i / 0x100000000)); - local b0, b1, b2, b3 = word2bytes(i); - return b0, b1, b2, b3, b4, b5, b6, b7; -end - -local F = function(x, y, z) return OR(AND(x, y), AND(NOT(x), z)); end -local G = function(x, y, z) return OR(AND(x, z), AND(y, NOT(z))); end -local H = function(x, y, z) return XOR(x, XOR(y, z)); end -local I = function(x, y, z) return XOR(y, OR(x, NOT(z))); end - -local MD5 = function(stream) - - local queue = Queue(); - - local A = 0x67452301; - local B = 0xefcdab89; - local C = 0x98badcfe; - local D = 0x10325476; - local public = {}; - - local processBlock = function() - local a = A; - local b = B; - local c = C; - local d = D; - - local X = {}; - - for i = 1, 16 do - X[i] = bytes2word(queue.pop(), queue.pop(), queue.pop(), queue.pop()); - end - - for i = 0, 63 do - local f, g, temp; - - if (0 <= i) and (i <= 15) then - f = F(b, c, d); - g = i; - elseif (16 <= i) and (i <= 31) then - f = G(b, c, d); - g = (5 * i + 1) % 16; - elseif (32 <= i) and (i <= 47) then - f = H(b, c, d); - g = (3 * i + 5) % 16; - elseif (48 <= i) and (i <= 63) then - f = I(b, c, d); - g = (7 * i) % 16; - end - temp = d; - d = c; - c = b; - b = b + LROT((a + f + CONSTANTS[i + 1] + X[g + 1]), SHIFT[i + 1]); - a = temp; - end - - A = AND(A + a, 0xFFFFFFFF); - B = AND(B + b, 0xFFFFFFFF); - C = AND(C + c, 0xFFFFFFFF); - D = AND(D + d, 0xFFFFFFFF); - end - - for s in stream do - queue.push(s); - if (queue.size() >= 64) then processBlock(); end - end - - local bits = queue.getHead() * 8; - - queue.push(0x80); - while ((queue.size() + 7) % 64) < 63 do - queue.push(0x00); - end - - local b0, b1, b2, b3, b4, b5, b6, b7 = dword2bytes(bits); - - queue.push(b0); - queue.push(b1); - queue.push(b2); - queue.push(b3); - queue.push(b4); - queue.push(b5); - queue.push(b6); - queue.push(b7); - - while queue.size() > 0 do - processBlock(); - end - - public.asBytes = function() - local b0, b1, b2, b3 = word2bytes(A); - local b4, b5, b6, b7 = word2bytes(B); - local b8, b9, b10, b11 = word2bytes(C); - local b12, b13, b14, b15 = word2bytes(D); - - return {b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15}; - end - - public.asHex = function() - local b0, b1, b2, b3 = word2bytes(A); - local b4, b5, b6, b7 = word2bytes(B); - local b8, b9, b10, b11 = word2bytes(C); - local b12, b13, b14, b15 = word2bytes(D); - - return string.format("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15); - end - - public.asString = function() - local b0, b1, b2, b3 = word2bytes(A); - local b4, b5, b6, b7 = word2bytes(B); - local b8, b9, b10, b11 = word2bytes(C); - local b12, b13, b14, b15 = word2bytes(D); - - return string.pack(string.rep('B', 16), - b0, b1, b2, b3, b4, b5, b6, b7, b8, - b9, b10, b11, b12, b13, b14, b15 - ) - end - - return public; - -end - -return MD5; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha1.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha1.lua deleted file mode 100644 index 589acf8..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha1.lua +++ /dev/null @@ -1,191 +0,0 @@ -local Bit = require(".crypto.util.bit"); -local Queue = require(".crypto.util.queue"); - -local AND = Bit.band; -local OR = Bit.bor; -local XOR = Bit.bxor; -local LROT = Bit.lrotate; -local LSHIFT = Bit.lshift; -local RSHIFT = Bit.rshift; - ---SHA1 is big-endian -local bytes2word = function(b0, b1, b2, b3) - local i = b0; i = LSHIFT(i, 8); - i = OR(i, b1); i = LSHIFT(i, 8); - i = OR(i, b2); i = LSHIFT(i, 8); - i = OR(i, b3); - return i; -end - -local word2bytes = function(word) - local b0, b1, b2, b3; - b3 = AND(word, 0xFF); word = RSHIFT(word, 8); - b2 = AND(word, 0xFF); word = RSHIFT(word, 8); - b1 = AND(word, 0xFF); word = RSHIFT(word, 8); - b0 = AND(word, 0xFF); - return b0, b1, b2, b3; -end - -local dword2bytes = function(i) - local b4, b5, b6, b7 = word2bytes(i); - local b0, b1, b2, b3 = word2bytes(math.floor(i / 0x100000000)); - return b0, b1, b2, b3, b4, b5, b6, b7; -end - -local F = function(x, y, z) return XOR(z, AND(x, XOR(y, z))); end -local G = function(x, y, z) return XOR(x, XOR(y, z)); end -local H = function(x, y, z) return OR(AND(x, OR(y, z)), AND(y, z)); end -local I = function(x, y, z) return XOR(x, XOR(y, z)); end - -local SHA1 = function() - - local queue = Queue(); - - local h0 = 0x67452301; - local h1 = 0xEFCDAB89; - local h2 = 0x98BADCFE; - local h3 = 0x10325476; - local h4 = 0xC3D2E1F0; - - local public = {}; - - local processBlock = function() - local a = h0; - local b = h1; - local c = h2; - local d = h3; - local e = h4; - local temp; - local k; - - local w = {}; - for i = 0, 15 do - w[i] = bytes2word(queue.pop(), queue.pop(), queue.pop(), queue.pop()); - end - - for i = 16, 79 do - w[i] = LROT((XOR(XOR(w[i - 3], w[i - 8]), XOR(w[i - 14], w[i - 16]))), 1); - end - - for i = 0, 79 do - if (i <= 19) then - temp = F(b, c, d); - k = 0x5A827999; - elseif (i <= 39) then - temp = G(b, c, d); - k = 0x6ED9EBA1; - elseif (i <= 59) then - temp = H(b, c, d); - k = 0x8F1BBCDC; - else - temp = I(b, c, d); - k = 0xCA62C1D6; - end - temp = LROT(a, 5) + temp + e + k + w[i]; - e = d; - d = c; - c = LROT(b, 30); - b = a; - a = temp; - end - - h0 = AND(h0 + a, 0xFFFFFFFF); - h1 = AND(h1 + b, 0xFFFFFFFF); - h2 = AND(h2 + c, 0xFFFFFFFF); - h3 = AND(h3 + d, 0xFFFFFFFF); - h4 = AND(h4 + e, 0xFFFFFFFF); - end - - public.init = function() - queue.reset(); - h0 = 0x67452301; - h1 = 0xEFCDAB89; - h2 = 0x98BADCFE; - h3 = 0x10325476; - h4 = 0xC3D2E1F0; - return public; - end - - - public.update = function(bytes) - for b in bytes do - queue.push(b); - if queue.size() >= 64 then processBlock(); end - end - - return public; - end - - public.finish = function() - local bits = queue.getHead() * 8; - - queue.push(0x80); - while ((queue.size() + 7) % 64) < 63 do - queue.push(0x00); - end - - local b0, b1, b2, b3, b4, b5, b6, b7 = dword2bytes(bits); - - queue.push(b0); - queue.push(b1); - queue.push(b2); - queue.push(b3); - queue.push(b4); - queue.push(b5); - queue.push(b6); - queue.push(b7); - - while queue.size() > 0 do - processBlock(); - end - - return public; - end - - public.asBytes = function() - local b0, b1, b2, b3 = word2bytes(h0); - local b4, b5, b6, b7 = word2bytes(h1); - local b8, b9, b10, b11 = word2bytes(h2); - local b12, b13, b14, b15 = word2bytes(h3); - local b16, b17, b18, b19 = word2bytes(h4); - - return {b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19}; - end - - public.asHex = function() - local b0, b1, b2, b3 = word2bytes(h0); - local b4, b5, b6, b7 = word2bytes(h1); - local b8, b9, b10, b11 = word2bytes(h2); - local b12, b13, b14, b15 = word2bytes(h3); - local b16, b17, b18, b19 = word2bytes(h4); - - return string.format("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19); - end - - public.asString = function() - local b0, b1, b2, b3 = word2bytes(h0); - local b4, b5, b6, b7 = word2bytes(h1); - local b8, b9, b10, b11 = word2bytes(h2); - local b12, b13, b14, b15 = word2bytes(h3); - local b16, b17, b18, b19 = word2bytes(h4); - - return string.pack(string.rep('B', 20), - b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19); - end - - return public; -end - - -local sha1 = function(stream) - local result = SHA1() - .update(stream) - .finish() - return result -end - -return { - sha1 = sha1, - SHA1 = SHA1 -} \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_256.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_256.lua deleted file mode 100644 index 64be951..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_256.lua +++ /dev/null @@ -1,230 +0,0 @@ -local Bit = require(".crypto.util.bit"); -local Queue = require(".crypto.util.queue"); -local Stream = require(".crypto.util.stream"); - -local CONSTANTS = { - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 }; - -local fmt = "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" .. - "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" - -local AND = Bit.band; -local OR = Bit.bor; -local NOT = Bit.bnot; -local XOR = Bit.bxor; -local RROT = Bit.rrotate; -local LSHIFT = Bit.lshift; -local RSHIFT = Bit.rshift; - ---SHA2 is big-endian -local bytes2word = function(b0, b1, b2, b3) - local i = b0; i = LSHIFT(i, 8); - i = OR(i, b1); i = LSHIFT(i, 8); - i = OR(i, b2); i = LSHIFT(i, 8); - i = OR(i, b3); - return i; -end - -local word2bytes = function(word) - local b0, b1, b2, b3; - b3 = AND(word, 0xFF); word = RSHIFT(word, 8); - b2 = AND(word, 0xFF); word = RSHIFT(word, 8); - b1 = AND(word, 0xFF); word = RSHIFT(word, 8); - b0 = AND(word, 0xFF); - return b0, b1, b2, b3; -end - -local dword2bytes = function(i) - local b4, b5, b6, b7 = word2bytes(i); - local b0, b1, b2, b3 = word2bytes(math.floor(i / 0x100000000)); - return b0, b1, b2, b3, b4, b5, b6, b7; -end - - -local SHA2_256 = function() - - local queue = Queue(); - - local h0 = 0x6a09e667; - local h1 = 0xbb67ae85; - local h2 = 0x3c6ef372; - local h3 = 0xa54ff53a; - local h4 = 0x510e527f; - local h5 = 0x9b05688c; - local h6 = 0x1f83d9ab; - local h7 = 0x5be0cd19; - - local public = {}; - - local processBlock = function() - local a = h0; - local b = h1; - local c = h2; - local d = h3; - local e = h4; - local f = h5; - local g = h6; - local h = h7; - - local w = {}; - - for i = 0, 15 do - w[i] = bytes2word(queue.pop(), queue.pop(), queue.pop(), queue.pop()); - end - - for i = 16, 63 do - local s0 = XOR(RROT(w[i - 15], 7), XOR(RROT(w[i - 15], 18), RSHIFT(w[i - 15], 3))); - local s1 = XOR(RROT(w[i - 2], 17), XOR(RROT(w[i - 2], 19), RSHIFT(w[i - 2], 10))); - w[i] = AND(w[i - 16] + s0 + w[i - 7] + s1, 0xFFFFFFFF); - end - - for i = 0, 63 do - local s1 = XOR(RROT(e, 6), XOR(RROT(e, 11), RROT(e, 25))); - local ch = XOR(AND(e, f), AND(NOT(e), g)); - local temp1 = h + s1 + ch + CONSTANTS[i + 1] + w[i]; - local s0 = XOR(RROT(a, 2), XOR(RROT(a, 13), RROT(a, 22))); - local maj = XOR(AND(a, b), XOR(AND(a, c), AND(b, c))); - local temp2 = s0 + maj; - - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - end - - h0 = AND(h0 + a, 0xFFFFFFFF); - h1 = AND(h1 + b, 0xFFFFFFFF); - h2 = AND(h2 + c, 0xFFFFFFFF); - h3 = AND(h3 + d, 0xFFFFFFFF); - h4 = AND(h4 + e, 0xFFFFFFFF); - h5 = AND(h5 + f, 0xFFFFFFFF); - h6 = AND(h6 + g, 0xFFFFFFFF); - h7 = AND(h7 + h, 0xFFFFFFFF); - end - - public.init = function() - queue.reset(); - - h0 = 0x6a09e667; - h1 = 0xbb67ae85; - h2 = 0x3c6ef372; - h3 = 0xa54ff53a; - h4 = 0x510e527f; - h5 = 0x9b05688c; - h6 = 0x1f83d9ab; - h7 = 0x5be0cd19; - - return public; - end - - public.update = function(bytes) - for b in bytes do - queue.push(b); - if queue.size() >= 64 then processBlock(); end - end - - return public; - end - - public.finish = function() - local bits = queue.getHead() * 8; - - queue.push(0x80); - while ((queue.size() + 7) % 64) < 63 do - queue.push(0x00); - end - - local b0, b1, b2, b3, b4, b5, b6, b7 = dword2bytes(bits); - - queue.push(b0); - queue.push(b1); - queue.push(b2); - queue.push(b3); - queue.push(b4); - queue.push(b5); - queue.push(b6); - queue.push(b7); - - while queue.size() > 0 do - processBlock(); - end - - return public; - end - - public.asBytes = function() - local b0, b1, b2, b3 = word2bytes(h0); - local b4, b5, b6, b7 = word2bytes(h1); - local b8, b9, b10, b11 = word2bytes(h2); - local b12, b13, b14, b15 = word2bytes(h3); - local b16, b17, b18, b19 = word2bytes(h4); - local b20, b21, b22, b23 = word2bytes(h5); - local b24, b25, b26, b27 = word2bytes(h6); - local b28, b29, b30, b31 = word2bytes(h7); - - - return { b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 - , b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31}; - end - - public.asHex = function() - local b0, b1, b2, b3 = word2bytes(h0); - local b4, b5, b6, b7 = word2bytes(h1); - local b8, b9, b10, b11 = word2bytes(h2); - local b12, b13, b14, b15 = word2bytes(h3); - local b16, b17, b18, b19 = word2bytes(h4); - local b20, b21, b22, b23 = word2bytes(h5); - local b24, b25, b26, b27 = word2bytes(h6); - local b28, b29, b30, b31 = word2bytes(h7); - - return string.format(fmt, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 - , b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31); - end - - public.asString = function() - local b0, b1, b2, b3 = word2bytes(h0); - local b4, b5, b6, b7 = word2bytes(h1); - local b8, b9, b10, b11 = word2bytes(h2); - local b12, b13, b14, b15 = word2bytes(h3); - local b16, b17, b18, b19 = word2bytes(h4); - local b20, b21, b22, b23 = word2bytes(h5); - local b24, b25, b26, b27 = word2bytes(h6); - local b28, b29, b30, b31 = word2bytes(h7); - - return string.pack(string.rep('B', 32), - b0, b1, b2, b3, b4, b5, b6, b7, b8, - b9, b10, b11, b12, b13, b14, b15, - b16, b17, b18, b19, b20, b21, b22, b23, b24, - b25, b26, b27, b28, b29, b30, b31); - end - - return public; - -end - ---- @class Stream : table - ---- @param stream (Stream) - A function that returns the next byte of the stream, or nil if the stream has ended. ---- @returns table - A table containing the hash in bytes, string, and hex formats. -local sha2_256 = function(stream) - local result = SHA2_256() - .update(stream) - .finish() - return result -end - -return { - sha2_256 = sha2_256, - SHA2_256 = SHA2_256 -}; diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_512.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_512.lua deleted file mode 100644 index 56298c7..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha2_512.lua +++ /dev/null @@ -1,107 +0,0 @@ -local Hex = require(".crypto.util.hex") - -local k512 = { -0x428a2f98d728ae22,0x7137449123ef65cd,0xb5c0fbcfec4d3b2f,0xe9b5dba58189dbbc, -0x3956c25bf348b538,0x59f111f1b605d019,0x923f82a4af194f9b,0xab1c5ed5da6d8118, -0xd807aa98a3030242,0x12835b0145706fbe,0x243185be4ee4b28c,0x550c7dc3d5ffb4e2, -0x72be5d74f27b896f,0x80deb1fe3b1696b1,0x9bdc06a725c71235,0xc19bf174cf692694, -0xe49b69c19ef14ad2,0xefbe4786384f25e3,0x0fc19dc68b8cd5b5,0x240ca1cc77ac9c65, -0x2de92c6f592b0275,0x4a7484aa6ea6e483,0x5cb0a9dcbd41fbd4,0x76f988da831153b5, -0x983e5152ee66dfab,0xa831c66d2db43210,0xb00327c898fb213f,0xbf597fc7beef0ee4, -0xc6e00bf33da88fc2,0xd5a79147930aa725,0x06ca6351e003826f,0x142929670a0e6e70, -0x27b70a8546d22ffc,0x2e1b21385c26c926,0x4d2c6dfc5ac42aed,0x53380d139d95b3df, -0x650a73548baf63de,0x766a0abb3c77b2a8,0x81c2c92e47edaee6,0x92722c851482353b, -0xa2bfe8a14cf10364,0xa81a664bbc423001,0xc24b8b70d0f89791,0xc76c51a30654be30, -0xd192e819d6ef5218,0xd69906245565a910,0xf40e35855771202a,0x106aa07032bbd1b8, -0x19a4c116b8d2d0c8,0x1e376c085141ab53,0x2748774cdf8eeb99,0x34b0bcb5e19b48a8, -0x391c0cb3c5c95a63,0x4ed8aa4ae3418acb,0x5b9cca4f7763e373,0x682e6ff3d6b2b8a3, -0x748f82ee5defb2fc,0x78a5636f43172f60,0x84c87814a1f0ab72,0x8cc702081a6439ec, -0x90befffa23631e28,0xa4506cebde82bde9,0xbef9a3f7b2c67915,0xc67178f2e372532b, -0xca273eceea26619c,0xd186b8c721c0c207,0xeada7dd6cde0eb1e,0xf57d4f7fee6ed178, -0x06f067aa72176fba,0x0a637dc5a2c898a6,0x113f9804bef90dae,0x1b710b35131c471b, -0x28db77f523047d84,0x32caab7b40c72493,0x3c9ebe0a15c9bebc,0x431d67c49c100d4c, -0x4cc5d4becb3e42b6,0x597f299cfc657e2a,0x5fcb6fab3ad6faec,0x6c44198c4a475817 -} - -local function pad128(msg, len) - local extra = 128 - ((len + 1 + 8) % 128) - len = string.pack(">I8", len * 8) - msg = msg .. "\128" .. string.rep("\0", extra) .. len - assert(#msg % 128 == 0) - return msg -end - -local ww512 = {} - ---- SHA-512 hash function. ---- @param msg string - The message to hash. ---- @returns table - A table containing the hash in bytes, string, and hex formats. -local function sha512 (msg) - msg = pad128(msg, #msg) - local h1, h2, h3, h4, h5, h6, h7, h8 = - 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, - 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, - 0x510e527fade682d1, 0x9b05688c2b3e6c1f, - 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179 - local k = k512 - local w = ww512 - local mlen = #msg - - for i = 1, mlen, 128 do - w[1], w[2], w[3], w[4], w[5], w[6], w[7], w[8], - w[9], w[10], w[11], w[12], w[13], w[14], w[15], w[16] - = string.unpack(">i8i8i8i8i8i8i8i8i8i8i8i8i8i8i8i8", msg, i) - -- mix msg block in state - - for j = 17, 80 do - local a = w[j-15] - local b = w[j-2] - w[j] = (a >> 1 ~ a >> 7 ~ a >> 8 ~ a << 56 ~ a << 63) - + (b >> 6 ~ b >> 19 ~ b >> 61 ~ b << 3 ~ b << 45) - + w[j-7] + w[j-16] - end - local a, b, c, d, e, f, g, h = h1, h2, h3, h4, h5, h6, h7, h8 - -- main state permutation - for j = 1, 80 do - local z = (e >> 14 ~ e >> 18 ~ e >> 41 ~ e << 23 - ~ e << 46 ~ e << 50) - + (g ~ e & (f ~ g)) + h + k[j] + w[j] - h = g - g = f - f = e - e = z + d - d = c - c = b - b = a - a = z + ((a ~ c) & d ~ a & c) - + (a >> 28 ~ a >> 34 ~ a >> 39 ~ a << 25 - ~ a << 30 ~ a << 36) - end - h1 = h1 + a - h2 = h2 + b - h3 = h3 + c - h4 = h4 + d - h5 = h5 + e - h6 = h6 + f - h7 = h7 + g - h8 = h8 + h - end - - local public = {} - - public.asBytes = function() - return { h1, h2, h3, h4, h5, h6, h7, h8} - end - - public.asString = function() - return string.pack(">i8i8i8i8i8i8i8i8", h1, h2, h3, h4, h5, h6, h7, h8) - end - - public.asHex = function() - return Hex.stringToHex(string.pack(">i8i8i8i8i8i8i8i8", h1, h2, h3, h4, h5, h6, h7, h8)) - end - - return public -end - -return sha512 diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha3.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha3.lua deleted file mode 100644 index bef4656..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/digest/sha3.lua +++ /dev/null @@ -1,235 +0,0 @@ -local Hex = require(".crypto.util.hex"); - -local ROUNDS = 24 - -local roundConstants = { -0x0000000000000001, -0x0000000000008082, -0x800000000000808A, -0x8000000080008000, -0x000000000000808B, -0x0000000080000001, -0x8000000080008081, -0x8000000000008009, -0x000000000000008A, -0x0000000000000088, -0x0000000080008009, -0x000000008000000A, -0x000000008000808B, -0x800000000000008B, -0x8000000000008089, -0x8000000000008003, -0x8000000000008002, -0x8000000000000080, -0x000000000000800A, -0x800000008000000A, -0x8000000080008081, -0x8000000000008080, -0x0000000080000001, -0x8000000080008008 -} - -local rotationOffsets = { --- ordered for [x][y] dereferencing, so appear flipped here: -{0, 36, 3, 41, 18}, -{1, 44, 10, 45, 2}, -{62, 6, 43, 15, 61}, -{28, 55, 25, 21, 56}, -{27, 20, 39, 8, 14} -} - - - --- the full permutation function -local function keccakF(st) - local permuted = st.permuted - local parities = st.parities - for round = 1, ROUNDS do - -- theta() - for x = 1,5 do - parities[x] = 0 - local sx = st[x] - for y = 1,5 do parities[x] = parities[x] ~ sx[y] end - end - -- - -- unroll the following loop - --for x = 1,5 do - -- local p5 = parities[(x)%5 + 1] - -- local flip = parities[(x-2)%5 + 1] ~ ( p5 << 1 | p5 >> 63) - -- for y = 1,5 do st[x][y] = st[x][y] ~ flip end - --end - local p5, flip, s - --x=1 - p5 = parities[2] - flip = parities[5] ~ (p5 << 1 | p5 >> 63) - s = st[1] - for y = 1,5 do s[y] = s[y] ~ flip end - --x=2 - p5 = parities[3] - flip = parities[1] ~ (p5 << 1 | p5 >> 63) - s = st[2] - for y = 1,5 do s[y] = s[y] ~ flip end - --x=3 - p5 = parities[4] - flip = parities[2] ~ (p5 << 1 | p5 >> 63) - s = st[3] - for y = 1,5 do s[y] = s[y] ~ flip end - --x=4 - p5 = parities[5] - flip = parities[3] ~ (p5 << 1 | p5 >> 63) - s = st[4] - for y = 1,5 do s[y] = s[y] ~ flip end - --x=5 - p5 = parities[1] - flip = parities[4] ~ (p5 << 1 | p5 >> 63) - s = st[5] - for y = 1,5 do s[y] = s[y] ~ flip end - - -- rhopi() - for y = 1,5 do - local py = permuted[y] - local r - for x = 1,5 do - s, r = st[x][y], rotationOffsets[x][y] - py[(2*x + 3*y)%5 + 1] = (s << r | s >> (64-r)) - end - end - - -- chi() - unroll the loop - --for x = 1,5 do - -- for y = 1,5 do - -- local combined = (~ permuted[(x)%5 +1][y]) & permuted[(x+1)%5 +1][y] - -- st[x][y] = permuted[x][y] ~ combined - -- end - --end - - local p, p1, p2 - --x=1 - s, p, p1, p2 = st[1], permuted[1], permuted[2], permuted[3] - for y = 1,5 do s[y] = p[y] ~ (~ p1[y]) & p2[y] end - --x=2 - s, p, p1, p2 = st[2], permuted[2], permuted[3], permuted[4] - for y = 1,5 do s[y] = p[y] ~ (~ p1[y]) & p2[y] end - --x=3 - s, p, p1, p2 = st[3], permuted[3], permuted[4], permuted[5] - for y = 1,5 do s[y] = p[y] ~ (~ p1[y]) & p2[y] end - --x=4 - s, p, p1, p2 = st[4], permuted[4], permuted[5], permuted[1] - for y = 1,5 do s[y] = p[y] ~ (~ p1[y]) & p2[y] end - --x=5 - s, p, p1, p2 = st[5], permuted[5], permuted[1], permuted[2] - for y = 1,5 do s[y] = p[y] ~ (~ p1[y]) & p2[y] end - - -- iota() - st[1][1] = st[1][1] ~ roundConstants[round] - end -end - - -local function absorb(st, buffer, algorithm) - - local blockBytes = st.rate / 8 - local blockWords = blockBytes / 8 - - -- append 0x01 byte and pad with zeros to block size (rate/8 bytes) - local totalBytes = #buffer + 1 - -- for keccak (2012 submission), the padding is byte 0x01 followed by zeros - -- for SHA3 (NIST, 2015), the padding is byte 0x06 followed by zeros - - if algorithm == "keccak" then - buffer = buffer .. ( '\x01' .. string.char(0):rep(blockBytes - (totalBytes % blockBytes))) - end - - if algorithm == "sha3" then - buffer = buffer .. ( '\x06' .. string.char(0):rep(blockBytes - (totalBytes % blockBytes))) - end - - totalBytes = #buffer - - --convert data to an array of u64 - local words = {} - for i = 1, totalBytes - (totalBytes % 8), 8 do - words[#words + 1] = string.unpack(' 1) then - out = Array.XOR(out, s); - else - out = s; - end - end - - return out; - end - - public.finish = function() - local blocks = math.ceil(dKeyLen / blockLen); - - dKey = {}; - - for b = 1, blocks do - local block = buildBlock(b); - dKey = Array.concat(dKey, block); - end - - if(Array.size(dKey) > dKeyLen) then dKey = Array.truncate(dKey, dKeyLen); end - - return public; - end - - public.asBytes = function() - return dKey; - end - - public.asHex = function() - return Array.toHex(dKey); - end - - public.asString = function() - return Array.toString(dKey); - end - - return public; -end - ---- @class Array : table - ---- PBKDF2 key derivation function ---- @param password (Array) - The password to derive the key from ---- @param salt (Array) - The salt to use ---- @param iterations number - The number of iterations to perform ---- @param keyLen number - The length of the key to derive ---- @param digest? string - The digest algorithm to use (sha1, sha256). Defaults to sha1. ---- @returns string - The derived key -local pbkdf2 = function(password, salt, iterations, keyLen, digest) - local Digest = nil - if digest == "sha1" then - Digest = SHA1.SHA1 - elseif digest == "sha256" then - Digest = SHA2_256.SHA2_256 - elseif digest == nil then - Digest = SHA1.SHA1 - else - error("Unsupported algorithm: " .. digest) - end - - local prf = HMAC.HMAC().setBlockSize(64).setDigest(Digest); - - local res = PBKDF2() - .setPRF(prf) - .setBlockLen(16) - .setDKeyLen(keyLen) - .setIterations(iterations) - .setSalt(salt) - .setPassword(password) - .finish() - - return res -end - -return { - PBKDF2 = PBKDF2, - pbkdf2 = pbkdf2 -}; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/hmac.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/hmac.lua deleted file mode 100644 index 470c4bf..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/hmac.lua +++ /dev/null @@ -1,126 +0,0 @@ -local Bit = require(".crypto.util.bit"); -local Stream = require(".crypto.util.stream"); -local Array = require(".crypto.util.array"); - -local SHA1 = require(".crypto.digest.sha1"); -local SHA2_256 = require(".crypto.digest.sha2_256"); - -local XOR = Bit.bxor; - -local HMAC = function() - local public = {}; - local blockSize = 64; - local Digest = nil; - local outerPadding = {}; - local innerPadding = {} - local digest; - - public.setBlockSize = function(bytes) - blockSize = bytes; - return public; - end - - public.setDigest = function(digestModule) - Digest = digestModule; - digest = Digest(); - return public; - end - - public.setKey = function(key) - local keyStream; - if Digest == nil then - error("Digest not set"); - end - if (Array.size(key) > blockSize) then - keyStream = Stream.fromArray(Digest() - .update(Stream.fromArray(key)) - .finish() - .asBytes()); - else - keyStream = Stream.fromArray(key); - end - - outerPadding = {}; - innerPadding = {}; - - for i = 1, blockSize do - local byte = keyStream(); - if byte == nil then byte = 0x00; end - outerPadding[i] = XOR(0x5C, byte); - innerPadding[i] = XOR(0x36, byte); - end - - return public; - end - - public.init = function() - digest.init() - .update(Stream.fromArray(innerPadding)); - return public; - end - - public.update = function(messageStream) - digest.update(messageStream); - return public; - end - - public.finish = function() - local inner = digest.finish().asBytes(); - digest.init() - .update(Stream.fromArray(outerPadding)) - .update(Stream.fromArray(inner)) - .finish(); - - return public; - end - - public.asBytes = function() - return digest.asBytes(); - end - - public.asHex = function() - return digest.asHex(); - end - - public.asString = function() - return digest.asString(); - end - - return public; -end - ---- @class Array : table ---- @class Stream : table - ---- HMAC function for generating a hash-based message authentication code ---- @param data (Stream) - The data to hash and authenticate ---- @param key (Array) - The key to use for the HMAC ---- @param algorithm? (string) - The algorithm to use for the HMAC (sha1, sha256). Defaults to "sha1" ---- @returns table - A table containing the HMAC in bytes, string, and hex formats. -local hmac = function(data, key, algorithm) - local digest = nil - if algorithm == "sha1" then - digest = SHA1.SHA1 - elseif algorithm == "sha256" then - digest = SHA2_256.SHA2_256 - elseif algorithm == nil then - digest = SHA1.SHA1 - else - error("Unsupported algorithm: " .. algorithm) - end - - local res = HMAC() - .setBlockSize(32) - .setDigest(digest) - .setKey(key) - .init() - .update(data) - .finish() - - return res -end - -return { - hmac = hmac, - HMAC = HMAC -}; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/init.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/init.lua deleted file mode 100644 index 65a507d..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/mac/init.lua +++ /dev/null @@ -1,8 +0,0 @@ -local Hmac = require(".crypto.mac.hmac") - -local mac = { - _version = "0.0.1", - createHmac = Hmac.hmac, -}; - -return mac \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/padding/zero.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/padding/zero.lua deleted file mode 100644 index 0a4f614..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/padding/zero.lua +++ /dev/null @@ -1,17 +0,0 @@ -local ZeroPadding = function(blockSize, byteCount) - - local paddingCount = blockSize - ((byteCount -1) % blockSize) + 1; - local bytesLeft = paddingCount; - - local stream = function() - if bytesLeft > 0 then - bytesLeft = bytesLeft - 1; - return 0x00; - else - return nil; - end - end - return stream; -end - -return ZeroPadding; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/array.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/array.lua deleted file mode 100644 index 4c0be04..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/array.lua +++ /dev/null @@ -1,222 +0,0 @@ - -local Bit = require(".crypto.util.bit"); -local Queue = require(".crypto.util.queue"); - -local XOR = Bit.bxor; - -local Array = {}; - -Array.size = function(array) - return #array; -end - -Array.fromString = function(string) - local bytes = {}; - - local i = 1; - local byte = string.byte(string, i); - while byte ~= nil do - bytes[i] = byte; - i = i + 1; - byte = string.byte(string, i); - end - - return bytes; - -end - -Array.toString = function(bytes) - local chars = {}; - local i = 1; - - local byte = bytes[i]; - while byte ~= nil do - chars[i] = string.char(byte); - i = i + 1; - byte = bytes[i]; - end - - return table.concat(chars, ""); -end - -Array.fromStream = function(stream) - local array = {}; - local i = 1; - - local byte = stream(); - while byte ~= nil do - array[i] = byte; - i = i + 1; - byte = stream(); - end - - return array; -end - -Array.readFromQueue = function(queue, size) - local array = {}; - - for i = 1, size do - array[i] = queue.pop(); - end - - return array; -end - -Array.writeToQueue = function(queue, array) - local size = Array.size(array); - - for i = 1, size do - queue.push(array[i]); - end -end - -Array.toStream = function(array) - local queue = Queue(); - local i = 1; - - local byte = array[i]; - while byte ~= nil do - queue.push(byte); - i = i + 1; - byte = array[i]; - end - - return queue.pop; -end - - -local fromHexTable = {}; -for i = 0, 255 do - fromHexTable[string.format("%02X", i)] = i; - fromHexTable[string.format("%02x", i)] = i; -end - -Array.fromHex = function(hex) - local array = {}; - - for i = 1, string.len(hex) / 2 do - local h = string.sub(hex, i * 2 - 1, i * 2); - array[i] = fromHexTable[h]; - end - - return array; -end - - -local toHexTable = {}; -for i = 0, 255 do - toHexTable[i] = string.format("%02X", i); -end - -Array.toHex = function(array) - local hex = {}; - local i = 1; - - local byte = array[i]; - while byte ~= nil do - hex[i] = toHexTable[byte]; - i = i + 1; - byte = array[i]; - end - - return table.concat(hex, ""); - -end - -Array.concat = function(a, b) - local concat = {}; - local out = 1; - - local i = 1; - local byte = a[i]; - while byte ~= nil do - concat[out] = byte; - i = i + 1; - out = out + 1; - byte = a[i]; - end - - i = 1; - byte = b[i]; - while byte ~= nil do - concat[out] = byte; - i = i + 1; - out = out + 1; - byte = b[i]; - end - - return concat; -end - -Array.truncate = function(a, newSize) - local x = {}; - - for i = 1, newSize do - x[i] = a[i]; - end - - return x; -end - -Array.XOR = function(a, b) - local x = {}; - - for k, v in pairs(a) do - x[k] = XOR(v, b[k]); - end - - return x; -end - -Array.substitute = function(input, sbox) - local out = {}; - - for k, v in pairs(input) do - out[k] = sbox[v]; - end - - return out; -end - -Array.permute = function(input, pbox) - local out = {}; - - for k, v in pairs(pbox) do - out[k] = input[v]; - end - - return out; -end - -Array.copy = function(input) - local out = {}; - - for k, v in pairs(input) do - out[k] = v; - end - return out; -end - -Array.slice = function(input, start, stop) - local out = {}; - - if start == nil then - start = 1 - elseif start < 0 then - start = #input + start + 1 - end - if stop == nil then - stop = #input - elseif stop < 0 then - stop = #input + stop + 1 - end - - for i = start, stop do - table.insert(out, input[i]) - end - - return out; -end - -return Array; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/bit.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/bit.lua deleted file mode 100644 index 0bbe448..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/bit.lua +++ /dev/null @@ -1,44 +0,0 @@ -local ok, e -ok = nil -if not ok then - ok, e = pcall(require, "bit") -- the LuaJIT one ? -end -if not ok then - ok, e = pcall(require, "bit32") -- Lua 5.2 -end -if not ok then - ok, e = pcall(require, "bit.numberlua") -- for Lua 5.1, https://github.com/tst2005/lua-bit-numberlua/ -end -if not ok then - error("no bitwise support found", 2) -end -assert(type(e) == "table", "invalid bit module") - --- Workaround to support Lua 5.2 bit32 API with the LuaJIT bit one -if e.rol and not e.lrotate then - e.lrotate = e.rol -end -if e.ror and not e.rrotate then - e.rrotate = e.ror -end - --- Workaround to support incomplete bit operations set -if not e.ror and not e.rrotate then - local ror = function(b, n) - return e.bor(e.rshift(b, n), e.lshift(b, 32 - n)) - end - - e.ror = ror - e.rrotate = ror -end - -if not e.rol and not e.lrotate then - local rol = function(b, n) - return e.bor(e.lshift(b, n), e.rshift(b, 32 - n)) - end - - e.rol = rol - e.lrotate = rol -end - -return e \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/hex.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/hex.lua deleted file mode 100644 index bcc7f2a..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/hex.lua +++ /dev/null @@ -1,46 +0,0 @@ - ---- Converts a string to its hexadecimal representation. ---- @param s string The input string. ---- @param ln? number - The number of characters per line. If not provided, the output will be a single line. ---- @param sep? string - The separator between each pair of hexadecimal characters. Defaults to an empty string. ---- @return string The - hexadecimal representation of the input string. -local function stringToHex(s, ln, sep) - if #s == 0 then return "" end - if not ln then - return (s:gsub('.', - function(c) return string.format('%02x', string.byte(c)) end - )) - end - sep = sep or "" - local t = {} - for i = 1, #s - 1 do - t[#t + 1] = string.format("%02x%s", s:byte(i), - (i % ln == 0) and '\n' or sep) - end - t[#t + 1] = string.format("%02x", s:byte(#s)) - return table.concat(t) -end - ---- Converts a hex encoded string to its corresponding decoded string. ---- If the optional parameter `unsafe` is defined, it assumes that the hex string is well-formed ---- (no checks, no whitespace removal). By default, it removes whitespace (including newlines) ---- and checks that the hex string is well-formed. ---- @param hs (string) The hex encoded string to be decoded. ---- @param unsafe (boolean) [optional] If true, assumes the hex string is well-formed. ---- @return (string) The decoded string. -local function hexToString(hs, unsafe) - local tonumber = tonumber - if not unsafe then - hs = string.gsub(hs, "%s+", "") -- remove whitespaces - if string.find(hs, '[^0-9A-Za-z]') or #hs % 2 ~= 0 then - error("invalid hex string") - end - end - local count = string.gsub(hs, '(%x%x)',function(c) return string.char(tonumber(c, 16)) end) - return count -end - -return { - stringToHex = stringToHex, - hexToString = hexToString, -} \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/init.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/init.lua deleted file mode 100644 index 80afdda..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/init.lua +++ /dev/null @@ -1,16 +0,0 @@ -local Bit = require(".crypto.util.bit") -local Queue = require(".crypto.util.queue") -local Stream = require(".crypto.util.stream") -local Hex = require(".crypto.util.hex") -local Array = require(".crypto.util.array") - -local util = { - _version = "0.0.1", - bit = Bit, - queue = Queue, - stream = Stream, - hex = Hex, - array = Array, -} - -return util diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/queue.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/queue.lua deleted file mode 100644 index 4818192..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/queue.lua +++ /dev/null @@ -1,47 +0,0 @@ -local Queue = function() - local queue = {}; - local tail = 0; - local head = 0; - - local public = {}; - - public.push = function(obj) - queue[head] = obj; - head = head + 1; - return; - end - - public.pop = function() - if tail < head - then - local obj = queue[tail]; - queue[tail] = nil; - tail = tail + 1; - return obj; - else - return nil; - end - end - - public.size = function() - return head - tail; - end - - public.getHead = function() - return head; - end - - public.getTail = function() - return tail; - end - - public.reset = function() - queue = {}; - head = 0; - tail = 0; - end - - return public; -end - -return Queue; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/stream.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/stream.lua deleted file mode 100644 index 49d52f5..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/crypto/util/stream.lua +++ /dev/null @@ -1,98 +0,0 @@ -local Queue = require(".crypto.util.queue"); - -local Stream = {}; - - -Stream.fromString = function(string) - local i = 0; - return function() - i = i + 1; - return string.byte(string, i); - end -end - - -Stream.toString = function(stream) - local array = {}; - local i = 1; - - local byte = stream(); - while byte ~= nil do - array[i] = string.char(byte); - i = i + 1; - byte = stream(); - end - - return table.concat(array); -end - - -Stream.fromArray = function(array) - local queue = Queue(); - local i = 1; - - local byte = array[i]; - while byte ~= nil do - queue.push(byte); - i = i + 1; - byte = array[i]; - end - - return queue.pop; -end - - -Stream.toArray = function(stream) - local array = {}; - local i = 1; - - local byte = stream(); - while byte ~= nil do - array[i] = byte; - i = i + 1; - byte = stream(); - end - - return array; -end - - -local fromHexTable = {}; -for i = 0, 255 do - fromHexTable[string.format("%02X", i)] = i; - fromHexTable[string.format("%02x", i)] = i; -end - -Stream.fromHex = function(hex) - local queue = Queue(); - - for i = 1, string.len(hex) / 2 do - local h = string.sub(hex, i * 2 - 1, i * 2); - queue.push(fromHexTable[h]); - end - - return queue.pop; -end - - - -local toHexTable = {}; -for i = 0, 255 do - toHexTable[i] = string.format("%02X", i); -end - -Stream.toHex = function(stream) - local hex = {}; - local i = 1; - - local byte = stream(); - while byte ~= nil do - hex[i] = toHexTable[byte]; - i = i + 1; - byte = stream(); - end - - return table.concat(hex); -end - -return Stream; \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/default.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/default.lua deleted file mode 100644 index 54b935a..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/default.lua +++ /dev/null @@ -1,22 +0,0 @@ --- default handler for aos -return function (insertInbox) - return function (msg) - -- Add Message to Inbox - insertInbox(msg) - - local txt = Colors.gray .. "New Message From " .. Colors.green .. - (msg.From and (msg.From:sub(1,3) .. "..." .. msg.From:sub(-3)) or "unknown") .. Colors.gray .. ": " - if msg.Action then - txt = txt .. Colors.gray .. (msg.Action and ("Action = " .. Colors.blue .. msg.Action:sub(1,20)) or "") .. Colors.reset - else - local data = msg.Data - if type(data) == 'table' then - data = require('json').encode(data) - end - txt = txt .. Colors.gray .. "Data = " .. Colors.blue .. (data and data:sub(1,20) or "") .. Colors.reset - end - -- Print to Output - print(txt) - end - -end \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/dump.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/dump.lua deleted file mode 100644 index d59e22f..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/dump.lua +++ /dev/null @@ -1,297 +0,0 @@ --- --- Copyright (C) 2018 Masatoshi Teruya --- --- Permission is hereby granted, free of charge, to any person obtaining a copy --- of this software and associated documentation files (the "Software"), to deal --- in the Software without restriction, including without limitation the rights --- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --- copies of the Software, and to permit persons to whom the Software is --- furnished to do so, subject to the following conditions: --- --- The above copyright notice and this permission notice shall be included in --- all copies or substantial portions of the Software. --- --- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN --- THE SOFTWARE. --- --- dump.lua --- lua-dump --- Created by Masatoshi Teruya on 18/04/22. --- ---- file-scope variables -local type = type -local floor = math.floor -local tostring = tostring -local tblsort = table.sort -local tblconcat = table.concat -local strmatch = string.match -local strformat = string.format ---- constants -local INFINITE_POS = math.huge -local LUA_FIELDNAME_PAT = '^[a-zA-Z_][a-zA-Z0-9_]*$' -local FOR_KEY = 'key' -local FOR_VAL = 'val' -local FOR_CIRCULAR = 'circular' -local RESERVED_WORD = { - -- primitive data - ['nil'] = true, - ['true'] = true, - ['false'] = true, - -- declaraton - ['local'] = true, - ['function'] = true, - -- boolean logic - ['and'] = true, - ['or'] = true, - ['not'] = true, - -- conditional statement - ['if'] = true, - ['elseif'] = true, - ['else'] = true, - -- iteration statement - ['for'] = true, - ['in'] = true, - ['while'] = true, - ['until'] = true, - ['repeat'] = true, - -- jump statement - ['break'] = true, - ['goto'] = true, - ['return'] = true, - -- block scope statement - ['then'] = true, - ['do'] = true, - ['end'] = true, -} -local DEFAULT_INDENT = 4 - ---- filter function for dump ---- @param val any ---- @param depth integer ---- @param vtype string ---- @param use string ---- @param key any ---- @param udata any ---- @return any val ---- @return boolean nodump -local function DEFAULT_FILTER(val) - return val -end - ---- sort_index ---- @param a table ---- @param b table -local function sort_index(a, b) - if a.typ == b.typ then - if a.typ == 'boolean' then - return b.key - end - - return a.key < b.key - end - - return a.typ == 'number' -end - ---- dumptbl ---- @param tbl table ---- @param depth integer ---- @param indent string ---- @param nestIndent string ---- @param ctx table ---- @return string -local function dumptbl(tbl, depth, indent, nestIndent, ctx) - local ref = tostring(tbl) - - -- circular reference - if ctx.circular[ref] then - local val, nodump = ctx.filter(tbl, depth, type(tbl), FOR_CIRCULAR, tbl, - ctx.udata) - - if val ~= nil and val ~= tbl then - local t = type(val) - - if t == 'table' then - -- dump table value - if not nodump then - return dumptbl(val, depth + 1, indent, nestIndent, ctx) - end - return tostring(val) - elseif t == 'string' then - return strformat('%q', val) - elseif t == 'number' or t == 'boolean' then - return tostring(val) - end - - return strformat('%q', tostring(val)) - end - - return '""' - end - - local res = {} - local arr = {} - local narr = 0 - local fieldIndent = indent .. nestIndent - - -- save reference - ctx.circular[ref] = true - - for k, v in pairs(tbl) do - -- check key - local key, nokdump = ctx.filter(k, depth, type(k), FOR_KEY, nil, - ctx.udata) - - if key ~= nil then - -- check val - local val, novdump = ctx.filter(v, depth, type(v), FOR_VAL, key, - ctx.udata) - local kv - - if val ~= nil then - local kt = type(key) - local vt = type(val) - - -- convert key to suitable to be safely read back - -- by the Lua interpreter - if kt == 'number' or kt == 'boolean' then - k = key - key = '[' .. tostring(key) .. ']' - -- dump table value - elseif kt == 'table' and not nokdump then - key = '[' .. - dumptbl(key, depth + 1, fieldIndent, nestIndent, - ctx) .. ']' - k = key - kt = 'string' - elseif kt ~= 'string' or RESERVED_WORD[key] or - not strmatch(key, LUA_FIELDNAME_PAT) then - key = strformat("[%q]", tostring(key), v) - k = key - kt = 'string' - end - - -- convert key-val pair to suitable to be safely read back - -- by the Lua interpreter - if vt == 'number' or vt == 'boolean' then - kv = strformat('%s%s = %s', fieldIndent, key, tostring(val)) - elseif vt == 'string' then - -- dump a string-value - if not novdump then - kv = strformat('%s%s = %q', fieldIndent, key, val) - else - kv = strformat('%s%s = %s', fieldIndent, key, val) - end - elseif vt == 'table' and not novdump then - kv = strformat('%s%s = %s', fieldIndent, key, dumptbl(val, - depth + - 1, - fieldIndent, - nestIndent, - ctx)) - else - kv = strformat('%s%s = %q', fieldIndent, key, tostring(val)) - end - - -- add to array - narr = narr + 1 - arr[narr] = { - typ = kt, - key = k, - val = kv, - } - end - end - end - - -- remove reference - ctx.circular[ref] = nil - -- concat result - if narr > 0 then - tblsort(arr, sort_index) - - for i = 1, narr do - res[i] = arr[i].val - end - res[1] = '{' .. ctx.LF .. res[1] - res = tblconcat(res, ',' .. ctx.LF) .. ctx.LF .. indent .. '}' - else - res = '{}' - end - - return res -end - ---- is_uint ---- @param v any ---- @return boolean ok -local function is_uint(v) - return type(v) == 'number' and v < INFINITE_POS and v >= 0 and floor(v) == v -end - ---- dump ---- @param val any ---- @param indent integer ---- @param padding integer ---- @param filter function ---- @param udata ---- @return string -local function dump(val, indent, padding, filter, udata) - local t = type(val) - - -- check indent - if indent == nil then - indent = DEFAULT_INDENT - elseif not is_uint(indent) then - error('indent must be unsigned integer', 2) - end - - -- check padding - if padding == nil then - padding = 0 - elseif not is_uint(padding) then - error('padding must be unsigned integer', 2) - end - - -- check filter - if filter == nil then - filter = DEFAULT_FILTER - elseif type(filter) ~= 'function' then - error('filter must be function', 2) - end - - -- dump table - if t == 'table' then - local ispace = '' - local pspace = '' - - if indent > 0 then - ispace = strformat('%' .. tostring(indent) .. 's', '') - end - - if padding > 0 then - pspace = strformat('%' .. tostring(padding) .. 's', '') - end - - return dumptbl(val, 1, pspace, ispace, { - LF = ispace == '' and ' ' or '\n', - circular = {}, - filter = filter, - udata = udata, - }) - end - - -- dump value - local v, nodump = filter(val, 0, t, FOR_VAL, nil, udata) - if nodump == true then - return tostring(v) - end - return strformat('%q', tostring(v)) -end - -return dump \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/eval.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/eval.lua deleted file mode 100644 index da3c629..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/eval.lua +++ /dev/null @@ -1,38 +0,0 @@ -local stringify = require(".stringify") --- handler for eval -return function (ao) - return function (msg) - -- exec expression - local expr = msg.Data - local func, err = load("return " .. expr, 'aos', 't', _G) - local output = "" - local e = nil - if err then - func, err = load(expr, 'aos', 't', _G) - end - if func then - output, e = func() - else - ao.outbox.Error = err - return - end - if e then - ao.outbox.Error = e - return - end - if HANDLER_PRINT_LOGS and output then - table.insert(HANDLER_PRINT_LOGS, type(output) == "table" and stringify.format(output) or tostring(output)) - else - -- set result in outbox.Output (Left for backwards compatibility) - ao.outbox.Output = { - json = type(output) == "table" and pcall(function () return json.encode(output) end) and output or "undefined", - data = { - output = type(output) == "table" and stringify.format(output) or output, - prompt = Prompt() - }, - prompt = Prompt() - } - - end - end -end diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.lua deleted file mode 100644 index ab6d53f..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.lua +++ /dev/null @@ -1,59 +0,0 @@ -local _utils = { _version = "0.0.2" } - -local _ = require('.utils') -local ao = require(".ao") - -function _utils.hasMatchingTag(name, value) - assert(type(name) == 'string' and type(value) == 'string', 'invalid arguments: (name : string, value : string)') - - return function (msg) - return msg.Tags[name] == value - end -end - -function _utils.hasMatchingTagOf(name, values) - assert(type(name) == 'string' and type(values) == 'table', 'invalid arguments: (name : string, values : string[])') - return function (msg) - for _, value in ipairs(values) do - local patternResult = Handlers.utils.hasMatchingTag(name, value)(msg) - - if patternResult ~= 0 and patternResult ~= false and patternResult ~= "skip" then - return patternResult - end - end - - return 0 - end -end - -function _utils.hasMatchingData(value) - assert(type(value) == 'string', 'invalid arguments: (value : string)') - return function (msg) - return msg.Data == value - end -end - -function _utils.reply(input) - assert(type(input) == 'table' or type(input) == 'string', 'invalid arguments: (input : table or string)') - return function (msg) - if type(input) == 'string' then - msg.reply({ Data = input}) - return - end - msg.reply(input) - end -end - -function _utils.continue(fn) - assert(type(fn) == 'function', 'invalid arguments: (fn : function)') - return function (msg) - local patternResult = fn(msg) - - if not patternResult or patternResult == 0 or patternResult == "skip" then - return patternResult - end - return 1 - end -end - -return _utils \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.md b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.md deleted file mode 100644 index a09793b..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers-utils.md +++ /dev/null @@ -1,138 +0,0 @@ -# README for Lua Module: \_utils (v0.0.1) - -## Overview - -The `_utils` module is a lightweight Lua utility library designed to provide common functionalities for handling and processing messages within the AOS computer system. It offers a set of functions to check message attributes and send replies, simplifying the development of more complex scripts and modules. This document will guide you through the module's functionalities, installation, and usage. - -### Version - -0.0.1 - -## Installation - -1. Ensure you have Lua installed on your AOS computer system. -2. Copy the `_utils.lua` file to your project directory or a designated Lua libraries directory. -3. Include the module in your Lua scripts using `local _utils = require('_utils')`. - -## Features - -### hasMatchingTag(name, value) - -Checks if a given message has a tag that matches the specified name and value. - -- **Parameters:** - - - `name` (string): The name of the tag to check. - - `value` (string): The value of the tag to match. - -- **Returns:** Function that takes a message object and returns `-1` if the tag matches, `0` otherwise. - -### hasMatchingTagOf(name, values) - -Checks if a given message has a tag that matches the specified name and one of the specified values. - -- **Parameters:** - - - `name` (string): The name of the tag to check. - - `values` (string): The values of which one should match. - -- **Returns:** Function that takes a message object and returns `-1` if the tag matches, `0` otherwise. - -### hasMatchingData(value) - -Checks if the message data matches the specified value. - -- **Parameters:** - - - `value` (string): The value to match against the message data. - -- **Returns:** Function that takes a message object and returns `-1` if the data matches, `0` otherwise. - -### reply(input) - -Sends a reply to the sender of a message. The reply can be a simple string or a table with more complex data and tags. - -- **Parameters:** - - - `input` (table or string): The content to send back. If a string, it sends it as data. If a table, it assumes a structure with `Tags`. - -- **Returns:** Function that takes a message object and sends the specified reply. - -### continue(fn) - -Inverts the provided pattern matching function's result if it matches, so that it continues execution with the next matching handler. - -- **Parameters:** - - - `fn` (function): Pattern matching function that returns `"skip"`, `false` or `0` if it does not match. - -- **Returns:** Function that executes the pattern matching function and returns `1` (continue), so that the execution of handlers continues. - -## Usage - -1. **Import the module:** - - ```lua - local _utils = require('_utils') - ``` - -2. **Check for a specific tag in a message:** - - ```lua - local isUrgent = _utils.hasMatchingTag('priority', 'urgent') - if isUrgent(message) == -1 then - print('This is an urgent message!') - end - ``` - -3. **Check for a specific tag with multiple possible values allowed:** - - ```lua - local isNotUrgent = _utils.hasMatchingTagOf('priority', { 'trivial', 'unimportant' }) - if isNotUrgent(message) == -1 then - print('This is not an urgent message!') - end - ``` - -4. **Check if the message data matches a value:** - - ```lua - local isHello = _utils.hasMatchingData('Hello') - if isHello(message) == -1 then - print('Someone says Hello!') - end - ``` - -5. **Reply to a message:** - - ```lua - local replyWithText = _utils.reply('Thank you for your message!') - replyWithText(message) - ``` - - Or with complex data and tags: - - ```lua - local replyWithTable = _utils.reply({Tags = {status = 'received'}}) - replyWithTable(message) - ``` - -6. **Continue execution shortcut:** - - ```lua - local isUrgent = _utils.continue(_utils.hasMatchingTag('priority', 'urgent')) - if isUrgent(message) ~= 0 then - print('This is an urgent message!') - end - if isUrgent(message) == -1 then return end - print('This message will continue') - ``` - -## Conventions and Requirements - -- This module assumes that the message objects provided to functions follow a specific structure with `Tags` and `Data` attributes. -- Error handling is implemented using assertions. Ensure that your AOS environment appropriately handles or logs assertion failures. - -## License - -MIT diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.lua deleted file mode 100644 index 6507a91..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.lua +++ /dev/null @@ -1,302 +0,0 @@ -local handlers = { _version = "0.0.5" } -local coroutine = require('coroutine') -local utils = require('.utils') - -handlers.utils = require('.handlers-utils') --- if update we need to keep defined handlers -if Handlers then - handlers.list = Handlers.list or {} - handlers.coroutines = Handlers.coroutines or {} -else - handlers.list = {} - handlers.coroutines = {} - -end -handlers.onceNonce = 0 - - -local function findIndexByProp(array, prop, value) - for index, object in ipairs(array) do - if object[prop] == value then - return index - end - end - return nil -end - -local function assertAddArgs(name, pattern, handle, maxRuns) - assert( - type(name) == 'string' and - (type(pattern) == 'function' or type(pattern) == 'table' or type(pattern) == 'string'), - 'Invalid arguments given. Expected: \n' .. - '\tname : string, ' .. - '\tpattern : Action : string | MsgMatch : table,\n' .. - '\t\tfunction(msg: Message) : {-1 = break, 0 = skip, 1 = continue},\n' .. - '\thandle(msg : Message) : void) | Resolver,\n' .. - '\tMaxRuns? : number | "inf" | nil') -end - -function handlers.generateResolver(resolveSpec) - return function(msg) - -- If the resolver is a single function, call it. - -- Else, find the first matching pattern (by its matchSpec), and exec. - if type(resolveSpec) == "function" then - return resolveSpec(msg) - else - for matchSpec, func in pairs(resolveSpec) do - if utils.matchesSpec(msg, matchSpec) then - return func(msg) - end - end - end - end -end - --- Returns the next message that matches the pattern --- This function uses Lua's coroutines under-the-hood to add a handler, pause, --- and then resume the current coroutine. This allows us to effectively block --- processing of one message until another is received that matches the pattern. -function handlers.receive(pattern) - local self = coroutine.running() - handlers.once(pattern, function (msg) - coroutine.resume(self, msg) - end) - return coroutine.yield(pattern) -end - -function handlers.once(...) - local name, pattern, handle - if select("#", ...) == 3 then - name = select(1, ...) - pattern = select(2, ...) - handle = select(3, ...) - else - name = "_once_" .. tostring(handlers.onceNonce) - handlers.onceNonce = handlers.onceNonce + 1 - pattern = select(1, ...) - handle = select(2, ...) - end - handlers.add(name, pattern, handle, 1) -end - -function handlers.add(...) - local name, pattern, handle, maxRuns - local args = select("#", ...) - if args == 2 then - name = select(1, ...) - pattern = select(1, ...) - handle = select(2, ...) - maxRuns = nil - elseif args == 3 then - name = select(1, ...) - pattern = select(2, ...) - handle = select(3, ...) - maxRuns = nil - else - name = select(1, ...) - pattern = select(2, ...) - handle = select(3, ...) - maxRuns = select(4, ...) - end - assertAddArgs(name, pattern, handle, maxRuns) - - handle = handlers.generateResolver(handle) - - -- update existing handler by name - local idx = findIndexByProp(handlers.list, "name", name) - if idx ~= nil and idx > 0 then - -- found update - handlers.list[idx].pattern = pattern - handlers.list[idx].handle = handle - handlers.list[idx].maxRuns = maxRuns - else - -- not found then add - table.insert(handlers.list, { pattern = pattern, handle = handle, name = name, maxRuns = maxRuns }) - - end - return #handlers.list -end - -function handlers.append(...) - local name, pattern, handle, maxRuns - local args = select("#", ...) - if args == 2 then - name = select(1, ...) - pattern = select(1, ...) - handle = select(2, ...) - maxRuns = nil - elseif args == 3 then - name = select(1, ...) - pattern = select(2, ...) - handle = select(3, ...) - maxRuns = nil - else - name = select(1, ...) - pattern = select(2, ...) - handle = select(3, ...) - maxRuns = select(4, ...) - end - assertAddArgs(name, pattern, handle, maxRuns) - - handle = handlers.generateResolver(handle) - -- update existing handler by name - local idx = findIndexByProp(handlers.list, "name", name) - if idx ~= nil and idx > 0 then - -- found update - handlers.list[idx].pattern = pattern - handlers.list[idx].handle = handle - handlers.list[idx].maxRuns = maxRuns - else - - table.insert(handlers.list, { pattern = pattern, handle = handle, name = name, maxRuns = maxRuns }) - end - - -end - -function handlers.prepend(...) - local name, pattern, handle, maxRuns - local args = select("#", ...) - if args == 2 then - name = select(1, ...) - pattern = select(1, ...) - handle = select(2, ...) - maxRuns = nil - elseif args == 3 then - name = select(1, ...) - pattern = select(2, ...) - handle = select(3, ...) - maxRuns = nil - else - name = select(1, ...) - pattern = select(2, ...) - handle = select(3, ...) - maxRuns = select(4, ...) - end - assertAddArgs(name, pattern, handle, maxRuns) - - handle = handlers.generateResolver(handle) - - -- update existing handler by name - local idx = findIndexByProp(handlers.list, "name", name) - if idx ~= nil and idx > 0 then - -- found update - handlers.list[idx].pattern = pattern - handlers.list[idx].handle = handle - handlers.list[idx].maxRuns = maxRuns - else - table.insert(handlers.list, 1, { pattern = pattern, handle = handle, name = name, maxRuns = maxRuns }) - end - - -end - -function handlers.before(handleName) - assert(type(handleName) == 'string', 'Handler name MUST be a string') - - local idx = findIndexByProp(handlers.list, "name", handleName) - return { - add = function (name, pattern, handle, maxRuns) - assertAddArgs(name, pattern, handle, maxRuns) - - handle = handlers.generateResolver(handle) - - if idx then - table.insert(handlers.list, idx, { pattern = pattern, handle = handle, name = name, maxRuns = maxRuns }) - end - - end - } -end - -function handlers.after(handleName) - assert(type(handleName) == 'string', 'Handler name MUST be a string') - local idx = findIndexByProp(handlers.list, "name", handleName) - return { - add = function (name, pattern, handle, maxRuns) - assertAddArgs(name, pattern, handle, maxRuns) - - handle = handlers.generateResolver(handle) - - if idx then - table.insert(handlers.list, idx + 1, { pattern = pattern, handle = handle, name = name, maxRuns = maxRuns }) - end - - end - } - -end - -function handlers.remove(name) - assert(type(name) == 'string', 'name MUST be string') - if #handlers.list == 1 and handlers.list[1].name == name then - handlers.list = {} - - end - - local idx = findIndexByProp(handlers.list, "name", name) - table.remove(handlers.list, idx) - -end - ---- return 0 to not call handler, -1 to break after handler is called, 1 to continue -function handlers.evaluate(msg, env) - local handled = false - assert(type(msg) == 'table', 'msg is not valid') - assert(type(env) == 'table', 'env is not valid') - - for _, o in ipairs(handlers.list) do - if o.name ~= "_default" then - local match = utils.matchesSpec(msg, o.pattern) - if not (type(match) == 'number' or type(match) == 'string' or type(match) == 'boolean') then - error("Pattern result is not valid, it MUST be string, number, or boolean") - end - - -- handle boolean returns - if type(match) == "boolean" and match == true then - match = -1 - elseif type(match) == "boolean" and match == false then - match = 0 - end - - -- handle string returns - if type(match) == "string" then - if match == "continue" then - match = 1 - elseif match == "break" then - match = -1 - else - match = 0 - end - end - - if match ~= 0 then - if match < 0 then - handled = true - end - -- each handle function can accept, the msg, env - local status, err = pcall(o.handle, msg, env) - if not status then - error(err) - end - -- remove handler if maxRuns is reached. maxRuns can be either a number or "inf" - if o.maxRuns ~= nil and o.maxRuns ~= "inf" then - o.maxRuns = o.maxRuns - 1 - if o.maxRuns == 0 then - handlers.remove(o.name) - end - end - end - if match < 0 then - return handled - end - end - end - -- do default - if not handled then - local idx = findIndexByProp(handlers.list, "name", "_default") - handlers.list[idx].handle(msg,env) - end -end - -return handlers \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.md b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.md deleted file mode 100644 index 0c0bdd7..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/handlers.md +++ /dev/null @@ -1,102 +0,0 @@ -# Lua Library: Handlers (Version 0.0.3) - -## Overview - -The Handlers library provides a flexible way to manage and execute a series of handlers based on patterns. Each handler consists of a pattern function, a handle function, and a name. This library is suitable for scenarios where different actions need to be taken based on varying input criteria. - -## Module Structure - -- `handlers._version`: String representing the version of the Handlers library. -- `handlers.list`: Table storing the list of registered handlers. - -## Functions - -### `handlers.append(name, pattern, handle)` - -Appends a new handler to the end of the handlers list. - -#### Parameters - -- `pattern` (function): Function that determines if the handler should be executed. -- `handle` (function): The handler function to execute. -- `name` (string): A unique name for the handler. - -### `handlers.prepend(name, pattern, handle)` - -Prepends a new handler to the beginning of the handlers list. - -#### Parameters - -- Same as `handlers.append`. - -### `handlers.before(handleName)` - -Returns an object that allows adding a new handler before a specified handler. - -#### Parameters - -- `handleName` (string): The name of the handler before which the new handler will be added. - -#### Returns - -- An object with an `add` method to insert the new handler. - -### `handlers.after(handleName)` - -Returns an object that allows adding a new handler after a specified handler. - -#### Parameters - -- `handleName` (string): The name of the handler after which the new handler will be added. - -#### Returns - -- An object with an `add` method to insert the new handler. - -### `handlers.remove(name)` - -Removes a handler from the handlers list by name. - -#### Parameters - -- `name` (string): The name of the handler to be removed. - -### `handlers.evaluate(msg, env)` - -Evaluates each handler against a given message and environment. Handlers are called in the order they appear in the handlers list. - -#### Parameters - -- `msg` (table): The message to be processed by the handlers. -- `env` (table): The environment in which the handlers are executed. - -#### Returns - -- `response` (varies): The response from the handler(s). Returns a default message if no handler matches. - -## Usage Example - -```lua -local handlers = require "handlers_module_path" - --- Define pattern and handle functions -local function myPattern(msg) - -- Determine if the handler should be executed -end - -local function myHandle(msg, env, response) - -- Handler logic -end - --- Append a new handler -handlers.append("myHandler", myPattern, myHandle) - --- Evaluate a message -local response = handlers.evaluate({ key = "value" }, { envKey = "envValue" }) -``` - -## Notes - -- Handlers are executed in the order they appear in `handlers.list`. -- The pattern function should return `0` to skip the handler, `-1` to break after the handler is executed, or `1` to continue with the next handler. -- The `evaluate` function can concatenate responses from multiple handlers. diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/package.json b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/package.json deleted file mode 100644 index 08ca6f1..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "type": "module", - "name": "process", - "version": "1.0.0", - "main": "index.js", - "license": "MIT", - "dependencies": { - "@permaweb/ao-loader": "^0.0.35" - }, - "scripts": { - "build": "ao build", - "test": "node --test --experimental-wasm-memory64", - "deploy": "ao publish -w ~/.wallet.json process.wasm -t Memory-Limit -v 1-gb -t Compute-Limit -v 9000000000000 -t Module-Format -v wasm64-unknown-emscripten-draft_2024_02_15 -t AOS-Version -v 2.0.0 -t Name -v aos-xl", - "deploy-sqlite": "ao publish -w ~/.wallet.json process.wasm -t Memory-Limit -v 1-gb -t Compute-Limit -v 9000000000000 -t Module-Format -v wasm64-unknown-emscripten-draft_2024_02_15 -t AOS-Version -v 2.0.0 -t Name -v sqlite-xl" - } -} diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/pretty.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/pretty.lua deleted file mode 100644 index 89ea8dc..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/pretty.lua +++ /dev/null @@ -1,20 +0,0 @@ -local pretty = { _version = "0.0.1"} - -function pretty.tprint (tbl, indent) - if not indent then indent = 0 end - local output = "" - for k, v in pairs(tbl) do - local formatting = string.rep(" ", indent) .. k .. ": " - if type(v) == "table" then - output = output .. formatting .. "\n" - output = output .. pretty.tprint(v, indent+1) - elseif type(v) == 'boolean' then - output = output .. formatting .. tostring(v) .. "\n" - else - output = output .. formatting .. v .. "\n" - end - end - return output -end - -return pretty \ No newline at end of file diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.lua deleted file mode 100644 index 30f4a33..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.lua +++ /dev/null @@ -1,371 +0,0 @@ -local pretty = require('.pretty') -local base64 = require('.base64') -local json = require('json') -local chance = require('.chance') -local crypto = require('.crypto.init') -local coroutine = require('coroutine') - -Colors = { - red = "\27[31m", - green = "\27[32m", - blue = "\27[34m", - reset = "\27[0m", - gray = "\27[90m" -} - -Bell = "\x07" - -Dump = require('.dump') -Utils = require('.utils') -Handlers = require('.handlers') -local stringify = require(".stringify") -local assignment = require('.assignment') -ao = nil -if _G.package.loaded['.ao'] then - ao = require('.ao') -elseif _G.package.loaded['ao'] then - ao = require('ao') -end --- Implement assignable polyfills on _ao -assignment.init(ao) - -local process = { _version = "2.0.0" } -local maxInboxCount = 10000 - --- wrap ao.send and ao.spawn for magic table -local aosend = ao.send -local aospawn = ao.spawn -ao.send = function (msg) - if msg.Data and type(msg.Data) == 'table' then - msg['Content-Type'] = 'application/json' - msg.Data = require('json').encode(msg.Data) - end - return aosend(msg) -end -ao.spawn = function (module, msg) - if msg.Data and type(msg.Data) == 'table' then - msg['Content-Type'] = 'application/json' - msg.Data = require('json').encode(msg.Data) - end - return aospawn(module, msg) -end - -local function removeLastThreeLines(input) - local lines = {} - for line in input:gmatch("([^\n]*)\n?") do - table.insert(lines, line) - end - - -- Remove the last three lines - for i = 1, 3 do - table.remove(lines) - end - - -- Concatenate the remaining lines - return table.concat(lines, "\n") -end - - -local function insertInbox(msg) - table.insert(Inbox, msg) - if #Inbox > maxInboxCount then - local overflow = #Inbox - maxInboxCount - for i = 1,overflow do - table.remove(Inbox, 1) - end - end -end - -local function findObject(array, key, value) - for i, object in ipairs(array) do - if object[key] == value then - return object - end - end - return nil -end - -function Tab(msg) - local inputs = {} - for _, o in ipairs(msg.Tags) do - if not inputs[o.name] then - inputs[o.name] = o.value - end - end - return inputs -end - -function Prompt() - return Colors.green .. Name .. Colors.gray - .. "@" .. Colors.blue .. "aos-" .. process._version .. Colors.gray - .. "[Inbox:" .. Colors.red .. tostring(#Inbox) .. Colors.gray - .. "]" .. Colors.reset .. "> " -end - -function print(a) - if type(a) == "table" then - a = stringify.format(a) - end - - local data = a - if ao.outbox.Output.data then - data = ao.outbox.Output.data .. "\n" .. a - end - ao.outbox.Output = { data = data, prompt = Prompt(), print = true } - - -- Only supported for newer version of AOS - if HANDLER_PRINT_LOGS then - table.insert(HANDLER_PRINT_LOGS, a) - return nil - end - - return tostring(a) -end - -function Send(msg) - if not msg.Target then - print("WARN: No target specified for message. Data will be stored, but no process will receive it.") - end - local result = ao.send(msg) - return { - output = "Message added to outbox", - receive = result.receive, - onReply = result.onReply - } -end - -function Spawn(...) - local module, spawnMsg - - if select("#", ...) == 1 then - spawnMsg = select(1, ...) - module = ao._module - else - module = select(1, ...) - spawnMsg = select(2, ...) - end - - if not spawnMsg then - spawnMsg = {} - end - local result = ao.spawn(module, spawnMsg) - return { - output = "Spawn process request added to outbox", - after = result.after, - receive = result.receive - } -end - -function Receive(match) - return Handlers.receive(match) -end - -function Assign(assignment) - if not ao.assign then - print("Assign is not implemented.") - return "Assign is not implemented." - end - ao.assign(assignment) - print("Assignment added to outbox.") - return 'Assignment added to outbox.' -end - -Seeded = Seeded or false - --- this is a temporary approach... -local function stringToSeed(s) - local seed = 0 - for i = 1, #s do - local char = string.byte(s, i) - seed = seed + char - end - return seed -end - -local function initializeState(msg, env) - if not Seeded then - --math.randomseed(1234) - chance.seed(tonumber(msg['Block-Height'] .. stringToSeed(msg.Owner .. msg.Module .. msg.Id))) - math.random = function (...) - local args = {...} - local n = #args - if n == 0 then - return chance.random() - end - if n == 1 then - return chance.integer(1, args[1]) - end - if n == 2 then - return chance.integer(args[1], args[2]) - end - return chance.random() - end - Seeded = true - end - Errors = Errors or {} - Inbox = Inbox or {} - - -- temporary fix for Spawn - if not Owner then - local _from = findObject(env.Process.Tags, "name", "From-Process") - if _from then - Owner = _from.value - else - Owner = msg.From - end - end - - if not Name then - local aosName = findObject(env.Process.Tags, "name", "Name") - if aosName then - Name = aosName.value - else - Name = 'aos' - end - end - -end - -function Version() - print("version: " .. process._version) -end - -function process.handle(msg, _) - env = nil - if _.Process then - env = _ - else - env = _.env - end - - ao.init(env) - -- relocate custom tags to root message - msg = ao.normalize(msg) - -- set process id - ao.id = ao.env.Process.Id - initializeState(msg, ao.env) - HANDLER_PRINT_LOGS = {} - - -- set os.time to return msg.Timestamp - os.time = function () return msg.Timestamp end - - -- tagify msg - msg.TagArray = msg.Tags - msg.Tags = Tab(msg) - -- tagify Process - ao.env.Process.TagArray = ao.env.Process.Tags - ao.env.Process.Tags = Tab(ao.env.Process) - -- magic table - if Content-Type == application/json - decode msg.Data to a Table - if msg.Tags['Content-Type'] and msg.Tags['Content-Type'] == 'application/json' then - msg.Data = require('json').decode(msg.Data or "{}") - end - -- init Errors - Errors = Errors or {} - -- clear Outbox - ao.clearOutbox() - - -- Only trust messages from a signed owner or an Authority - if msg.From ~= msg.Owner and not ao.isTrusted(msg) then - Send({Target = msg.From, Data = "Message is not trusted by this process!"}) - print('Message is not trusted! From: ' .. msg.From .. ' - Owner: ' .. msg.Owner) - return ao.result({ }) - end - - if ao.isAssignment(msg) and not ao.isAssignable(msg) then - Send({Target = msg.From, Data = "Assignment is not trusted by this process!"}) - print('Assignment is not trusted! From: ' .. msg.From .. ' - Owner: ' .. msg.Owner) - return ao.result({ }) - end - - Handlers.add("_eval", - function (msg) - return msg.Action == "Eval" and Owner == msg.From - end, - require('.eval')(ao) - ) - Handlers.append("_default", function () return true end, require('.default')(insertInbox)) - -- call evaluate from handlers passing env - msg.reply = - function(replyMsg) - replyMsg.Target = msg["Reply-To"] or (replyMsg.Target or msg.From) - replyMsg["X-Reference"] = msg["X-Reference"] or msg.Reference - replyMsg["X-Origin"] = msg["X-Origin"] or nil - - return ao.send(replyMsg) - end - - msg.forward = - function(target, forwardMsg) - -- Clone the message and add forwardMsg tags - local newMsg = ao.sanitize(msg) - forwardMsg = forwardMsg or {} - - for k,v in pairs(forwardMsg) do - newMsg[k] = v - end - - -- Set forward-specific tags - newMsg.Target = target - newMsg["Reply-To"] = msg["Reply-To"] or msg.From - newMsg["X-Reference"] = msg["X-Reference"] or msg.Reference - newMsg["X-Origin"] = msg["X-Origin"] or msg.From - -- clear functions - newMsg.reply = nil - newMsg.forward = nil - - ao.send(newMsg) - end - - local co = coroutine.create( - function() - return pcall(Handlers.evaluate, msg, ao.env) - end - ) - local _, status, result = coroutine.resume(co) - - -- Make sure we have a reference to the coroutine if it will wake up. - -- Simultaneously, prune any dead coroutines so that they can be - -- freed by the garbage collector. - table.insert(Handlers.coroutines, co) - for i, x in ipairs(Handlers.coroutines) do - if coroutine.status(x) == "dead" then - table.remove(Handlers.coroutines, i) - end - end - - if not status then - if (msg.Action == "Eval") then - table.insert(Errors, result) - local printData = table.concat(HANDLER_PRINT_LOGS, "\n") - return { Error = printData .. '\n\n' .. result } - end - --table.insert(Errors, result) - --ao.outbox.Output.data = "" - if msg.Action then - print(Colors.red .. "Error" .. Colors.gray .. " handling message with Action = " .. msg.Action .. Colors.reset) - else - print(Colors.red .. "Error" .. Colors.gray .. " handling message " .. Colors.reset) - end - print(Colors.green .. result .. Colors.reset) - print("\n" .. Colors.gray .. removeLastThreeLines(debug.traceback()) .. Colors.reset) - return ao.result({ Messages = {}, Spawns = {}, Assignments = {} }) - end - - if msg.Action == "Eval" then - local response = ao.result({ - Output = { - data = table.concat(HANDLER_PRINT_LOGS, "\n"), - prompt = Prompt(), - test = Dump(HANDLER_PRINT_LOGS) - } - }) - HANDLER_PRINT_LOGS = {} -- clear logs - return response - else - local response = ao.result({ Output = { data = table.concat(HANDLER_PRINT_LOGS, "\n"), prompt = Prompt(), print = true } }) - HANDLER_PRINT_LOGS = {} -- clear logs - return response - end -end - -return process diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.md b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.md deleted file mode 100644 index 105cf9a..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/process.md +++ /dev/null @@ -1,87 +0,0 @@ -# Lua Library: Process (Version 0.1.2) - -## Overview - -The Process library provides an environment for managing and executing processes on the AO network. It includes capabilities for handling messages, spawning processes, and customizing the environment with programmable logic and handlers. - -## Dependencies - -This module requires several external libraries: - -- `JSON`: For handling JSON data. -- `pretty`: For pretty-printing tables. -- `base64`: For encoding and decoding base64 strings. -- `ao`: For managing AO-specific operations like sending messages. -- `handlers`: For managing and executing custom handler functions. - -## Module Structure - -- `process._version`: String representing the version of the Process library. -- `manpages`: A table storing manual pages for different functions or modules. - -## Functions - -### `prompt()` - -Returns a custom command prompt string. - -#### Returns - -- `string`: The command prompt. - -### `initializeState(msg, env)` - -Initializes or updates the state of the process based on the incoming message and environment. - -#### Parameters - -- `msg` (table): The incoming message. -- `env` (table): The environment in which the process is operating. - -### `version()` - -Prints the version of the Process library. - -### `man(page)` - -Returns the manual page for a given topic. - -#### Parameters - -- `page` (string, optional): The name of the manual page. - -#### Returns - -- `string`: The content of the manual page. - -### `process.handle(msg, env)` - -Main handler for processing incoming messages. It initializes the state, processes commands, and handles message evaluation and inbox management. - -#### Parameters - -- `msg` (table): The message to be handled. -- `env` (table): The environment of the process. - -#### Returns - -- Varies: The response based on the processed message. - -## Usage Example - -```lua -local process = require "process_module_path" - --- Example message and environment -local msg = { /* message structure */ } -local env = { /* environment structure */ } - --- Handle a message -local response = process.handle(msg, env) -``` - -## Notes - -- The `process.handle` function is the core of this module, determining how messages are processed, including evaluating expressions, managing manual pages, and calling custom handlers. -- This module is designed to be used within the AO network environment, leveraging the AO and Handlers libraries for communication and process management. -- Manual pages (`manpages`) provide documentation and guidance for users of the aos system. diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/repl.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/repl.js deleted file mode 100644 index bde44b0..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/repl.js +++ /dev/null @@ -1,81 +0,0 @@ -const readline = require('readline'); -const AoLoader = require('@permaweb/ao-loader'); -const fs = require('fs'); -const wasm = fs.readFileSync('./process.wasm'); - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, -}); - -const env = { - Process: { - Id: 'PROCESS_TEST', - Owner: 'TOM', - }, -}; -let prompt = 'aos'; - -async function repl(state) { - const handle = await AoLoader(wasm); - - rl.question(prompt + '> ', async function (line) { - // Exit the REPL if the user types "exit" - if (line === 'exit') { - console.log('Exiting...'); - rl.close(); - return; - } - let response = {}; - // Evaluate the JavaScript code and print the result - try { - const message = createMessage(line); - response = handle(state, message, env); - console.log(response.Output); - if (response.Output.data.output) { - console.log(response.Output.data.output); - } - //console.log(response.messages) - if (response.Output.data.prompt) { - prompt = response.Output.data.prompt; - } - - // Continue the REPL - await repl(response.buffer); - } catch (err) { - console.log('Error:', err); - process.exit(0); - } - }); -} - -repl(null); - -function createMessage(expr) { - return { - Owner: 'TOM', - Target: 'PROCESS', - Tags: [ - { name: 'Data-Protocol', value: 'ao' }, - { name: 'Variant', value: 'ao.TN.1' }, - { name: 'Type', value: 'message' }, - { name: 'function', value: 'eval' }, - { name: 'expression', value: expr }, - ], - }; -} - -/** - * const spawn = { - owner: "TOM", - tags: [ - - { name: "Data-Protocol", value: "ao" }, - { name: "ao-type", value: "spawn" }, - { name: "function", value: "eval" }, - { name: "expression", value: expr } - - ] -} - - */ diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/stringify.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/stringify.lua deleted file mode 100644 index 3288030..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/stringify.lua +++ /dev/null @@ -1,79 +0,0 @@ -local stringify = { _version = "0.0.1" } - --- ANSI color codes -local colors = { - red = "\27[31m", - green = "\27[32m", - blue = "\27[34m", - reset = "\27[0m" -} - -function stringify.isSimpleArray(tbl) - local arrayIndex = 1 - for k, v in pairs(tbl) do - if k ~= arrayIndex or (type(v) ~= "number" and type(v) ~= "string") then - return false - end - arrayIndex = arrayIndex + 1 - end - return true -end - -function stringify.format(tbl, indent) - indent = indent or 0 - local toIndent = string.rep(" ", indent) - local toIndentChild = string.rep(" ", indent + 2) - - local result = {} - local isArray = true - local arrayIndex = 1 - - if stringify.isSimpleArray(tbl) then - for _, v in ipairs(tbl) do - if type(v) == "string" then - v = colors.green .. '"' .. v .. '"' .. colors.reset - else - v = colors.blue .. tostring(v) .. colors.reset - end - table.insert(result, v) - end - return "{ " .. table.concat(result, ", ") .. " }" - end - - for k, v in pairs(tbl) do - if isArray then - if k == arrayIndex then - arrayIndex = arrayIndex + 1 - if type(v) == "table" then - v = stringify.format(v, indent + 2) - elseif type(v) == "string" then - v = colors.green .. '"' .. v .. '"' .. colors.reset - else - v = colors.blue .. tostring(v) .. colors.reset - end - table.insert(result, toIndentChild .. v) - else - isArray = false - result = {} - end - end - if not isArray then - if type(v) == "table" then - v = stringify.format(v, indent + 2) - elseif type(v) == "string" then - v = colors.green .. '"' .. v .. '"' .. colors.reset - else - v = colors.blue .. tostring(v) .. colors.reset - end - k = colors.red .. k .. colors.reset - table.insert(result, toIndentChild .. k .. " = " .. v) - end - end - - local prefix = isArray and "{\n" or "{\n " - local suffix = isArray and "\n" .. toIndent .. " }" or "\n" .. toIndent .. "}" - local separator = isArray and ",\n" or ",\n " - return prefix .. table.concat(result, separator) .. suffix -end - -return stringify diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/assignment.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/assignment.test.js deleted file mode 100644 index 7bd4180..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/assignment.test.js +++ /dev/null @@ -1,476 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import * as assert from 'node:assert'; -import fs from 'node:fs'; -import { describe, test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; - -const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, -}; - -describe('add the assignable MatchSpec', async () => { - test('by name', async () => { - const handle = await AoLoader(wasm, options); - - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Thomas' }, - ], - Data: ` - ao.addAssignable('foobar', function (msg) return true end) - Send({ Target = "TEST", Data = { length = #ao.assignables, name = ao.assignables[1].name } }) - `, - }; - - const result = await handle(null, msg, env); - - assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { - length: 1, - name: 'foobar', - }); - }); - - test('update by name', async () => { - const handle = await AoLoader(wasm, options); - - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Thomas' }, - ], - Data: ` - ao.addAssignable('foobar', function (msg) return true end) - ao.addAssignable('foobar', function (msg) return false end) - Send({ Target = "TEST", Data = { length = #ao.assignables, name = ao.assignables[1].name } }) - `, - }; - - const result = await handle(null, msg, env); - - assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { - length: 1, - name: 'foobar', - }); - }); - - test('by index', async () => { - const handle = await AoLoader(wasm, options); - - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Thomas' }, - ], - Data: ` - ao.addAssignable(function (msg) return true end) - Send({ Target = "TEST", Data = { length = #ao.assignables, name = ao.assignables[1].name } }) - `, - }; - - const result = await handle(null, msg, env); - - assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { length: 1 }); - }); - - test('require name to be a string', async () => { - const handle = await AoLoader(wasm, options); - - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Thomas' }, - ], - Data: ` - ao.addAssignable(1234, function (msg) return true end) - `, - }; - - const result = await handle(null, msg, env); - - assert.ok(result.Error.includes('MatchSpec name MUST be a string')); - }); -}); - -describe('remove the assignable MatchSpec', () => { - test('by name', async () => { - const handle = await AoLoader(wasm, options); - - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Thomas' }, - ], - Data: ` - ao.addAssignable(function (msg) return true end) - ao.addAssignable('foobar', function (msg) return true end) - Send({ Target = "TEST", Data = { length = #ao.assignables, name = ao.assignables[2].name } }) - - ao.removeAssignable('foobar') - Send({ Target = "TEST", Data = { length = #ao.assignables } }) - `, - }; - - const result = await handle(null, msg, env); - - assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { - length: 2, - name: 'foobar', - }); - assert.deepStrictEqual(JSON.parse(result.Messages[1].Data), { length: 1 }); - }); - - test('by index', async () => { - const handle = await AoLoader(wasm, options); - - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Thomas' }, - ], - Data: ` - ao.addAssignable(function (msg) return true end) - ao.addAssignable('foobar', function (msg) return true end) - Send({ Target = "TEST", Data = { length = #ao.assignables, name = ao.assignables[2].name } }) - - ao.removeAssignable(1) - Send({ Target = "TEST", Data = { length = #ao.assignables, name = ao.assignables[1].name } }) - `, - }; - - const result = await handle(null, msg, env); - - assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { - length: 2, - name: 'foobar', - }); - assert.deepStrictEqual(JSON.parse(result.Messages[1].Data), { - length: 1, - name: 'foobar', - }); - }); - - test('require name to be a string or number', async () => { - const handle = await AoLoader(wasm, options); - - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Thomas' }, - ], - Data: ` - ao.removeAssignable({}) - `, - }; - - const result = await handle(null, msg, env); - assert.ok(result.Error.includes('index MUST be a number')); - }); -}); - -describe('determine whether the msg is an assignment or not', () => { - test('is an assignment', async () => { - const handle = await AoLoader(wasm, options); - - const addAssignableMsg = { - Target: 'AOS', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Thomas' }, - ], - Data: ` - ao.addAssignable(function (msg) return true end) - Handlers.add( - "IsAssignment", - function (Msg) return Msg.Action == 'IsAssignment' end, - function (Msg) - Send({ Target = 'TEST', Data = { id = Msg.Id, isAssignment = ao.isAssignment(Msg) } }) - end - ) - `, - }; - - const { Memory } = await handle(null, addAssignableMsg, env); - - const msg = { - Target: 'NOT_AOS', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'IsAssignment' }, - { name: 'Name', value: 'Thomas' }, - ], - Data: 'foobar', - }; - - const result = await handle(Memory, msg, env); - assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { - id: '1234xyxfoo', - isAssignment: true, - }); - }); - - test('is NOT an assignment', async () => { - const handle = await AoLoader(wasm, options); - - const addAssignableMsg = { - Target: 'AOS', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Thomas' }, - ], - Data: ` - ao.addAssignable(function (msg) return true end) - Handlers.add( - "IsAssignment", - function (Msg) return Msg.Action == 'IsAssignment' end, - function (Msg) - Send({ Target = 'TEST', Data = { id = Msg.Id, isAssignment = ao.isAssignment(Msg) } }) - end - ) - `, - }; - - const { Memory } = await handle(null, addAssignableMsg, env); - - const msg = { - Target: 'AOS', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'IsAssignment' }, - { name: 'Name', value: 'Thomas' }, - ], - Data: 'foobar', - }; - - const result = await handle(Memory, msg, env); - - assert.deepStrictEqual(JSON.parse(result.Messages[0].Data), { - id: '1234xyxfoo', - isAssignment: false, - }); - }); -}); - -describe('run handles on assignment based on assignables configured', () => { - test('at least 1 assignable allows specific assignment', async () => { - const handle = await AoLoader(wasm, options); - - const addAssignableMsg = { - Target: 'AOS', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Thomas' }, - ], - Data: ` - ao.addAssignable(function (msg) return msg.Name == 'Frank' end) - ao.addAssignable(function (msg) return msg.Name == 'Thomas' end) - `, - }; - - const { Memory } = await handle(null, addAssignableMsg, env); - - const msg = { - Target: 'NOT_AOS', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Thomas' }, - ], - Data: '2 + 2', - }; - - const result = await handle(Memory, msg, env); - assert.deepStrictEqual(result.Output.data, '4'); - }); - - test('assignables do NOT allow specific assignment', async () => { - const handle = await AoLoader(wasm, options); - - const addAssignableMsg = { - Target: 'AOS', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Thomas' }, - ], - Data: ` - ao.addAssignable(function (msg) return msg.Name == 'Frank' end) - ao.addAssignable(function (msg) return msg.Name == 'Thomas' end) - `, - }; - - const { Memory } = await handle(null, addAssignableMsg, env); - - const msg = { - Target: 'NOT_AOS', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Not-Thomas' }, - ], - Data: '2 + 2', - }; - - const result = await handle(Memory, msg, env); - assert.deepStrictEqual( - result.Messages[0].Data, - 'Assignment is not trusted by this process!', - ); - }); - - test('assignable does NOT allow specific assignment', async () => { - const handle = await AoLoader(wasm, options); - - const addAssignableMsg = { - Target: 'AOS', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Thomas' }, - ], - Data: ` - ao.addAssignable(function (msg) return msg.Name == 'Thomas' end) - `, - }; - - const { Memory } = await handle(null, addAssignableMsg, env); - - const msg = { - Target: 'NOT_AOS', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Not-Thomas' }, - ], - Data: '2 + 2', - }; - - const result = await handle(Memory, msg, env); - assert.deepStrictEqual( - result.Messages[0].Data, - 'Assignment is not trusted by this process!', - ); - }); - - test('no assignables defaults to no assignments allowed', async () => { - const handle = await AoLoader(wasm, options); - - const msg = { - Target: 'NOT_AOS', - Owner: 'FOOBAR', - 'Block-Height': '1000', - Id: '1234xyxfoo', - From: 'FOOBAR', - Module: 'WOOPAWOOPA', - Tags: [ - { name: 'Action', value: 'Eval' }, - { name: 'Name', value: 'Not-Thomas' }, - ], - Data: '2 + 2', - }; - - const result = await handle(null, msg, env); - assert.deepStrictEqual( - result.Messages[0].Data, - 'Assignment is not trusted by this process!', - ); - }); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/aes.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/aes.test.js deleted file mode 100644 index ca0a51c..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/aes.test.js +++ /dev/null @@ -1,95 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { - format: 'wasm64-unknown-emscripten-draft_2024_02_15', - computeLimit: 10024704733, -}; - -test('run aes cipher successfully', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - - const results = [ - // AES128 CBC Mode - 'A3B9E6E1FBD9D46930E5F76807C84B8E', - '616F0000000000000000000000000000', - // AES128 ECB Mode - '3FF54BD61AD1AA06BC367A10575CC7C5', - '616F0000000000000000000000000000', - // AES128 CFB Mode - '1DA7169C093D6B23160B6785B28E4BED', - '616F0000000000000000000000000000', - // AES128 OFB Mode - '1DA7169C093D6B23160B6785B28E4BED', - '616F0000000000000000000000000000', - // AES128 CTR Mode - '1DA7169C093D6B23160B6785B28E4BED', - '616F0000000000000000000000000000', - ]; - - const data = ` - local crypto = require(".crypto") - local Hex = require(".crypto.util.hex") - - local modes = { "CBC", "ECB", "CFB", "OFB", "CTR" } - local iv = "super_secret_shh" - - local key_128 = "super_secret_shh" - local key_192 = "super_secret_password_sh" - local key_256 = "super_duper_secret_password_shhh" - local results = {} - - local run = function(keySize, modes) - for _, mode in ipairs(modes) do - local key = key_128 - if keySize == 192 then - key = key_192 - elseif keySize == 256 then - key = key_256 - end - - - local l_iv = iv - if mode == "ECB" then - l_iv = "" - end - - local encrypted = crypto.cipher.aes.encrypt("ao", key, l_iv, mode, keySize).asHex() - local decrypted = crypto.cipher.aes.decrypt(encrypted, key, l_iv, mode, keySize).asHex() - results[#results + 1] = encrypted - results[#results + 1] = decrypted - end - end - - run(128, modes) - -- run(192, modes) - -- run(256, modes) - return table.concat(results, ", ") - `; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: data, - }; - - const result = await handle(null, msg, env); - - assert.equal(result.Output?.data, results.join(', ')); - // assert.ok(result.GasUsed >= 3000000000) - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/issac.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/issac.test.js deleted file mode 100644 index c87ab5c..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/issac.test.js +++ /dev/null @@ -1,52 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test('run issac cipher successfully', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - - const results = ['7851', 'ao']; - - const data = ` - local crypto = require(".crypto"); - - local results = {}; - - local message = "ao"; - local key = "secret_key"; - - local encrypted, decrypted; - - encrypted = crypto.cipher.issac.encrypt(message, key); - decrypted = crypto.cipher.issac.decrypt(encrypted.asString(), key); - - results[1] = encrypted.asHex(); - results[2] = decrypted; - - return table.concat(results, ", "); - `; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: data, - }; - - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results.join(', ')); - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/morus.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/morus.test.js deleted file mode 100644 index 4748743..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/morus.test.js +++ /dev/null @@ -1,71 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); - -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test('run morus cipher successfully', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - - const results = [ - '514ed31473d8fb0b76c6cbb17af35ed01d0a', - 'ao', - '6164646974696f6e616c20646174616aae7a8b95c50047bea251c3b7133eec5fcc', - 'ao', - ]; - - const data = ` - local crypto = require(".crypto"); - - local results = {}; - - local m = "ao"; - - --[[ - 16 bit key - ]]-- - - local k = "super_secret_shh" - local iv = "0000000000000000" - local ad= ""; - - local e = crypto.cipher.morus.encrypt(k, iv, m, ad); - results[1] = e.asHex(); - results[2] = crypto.cipher.morus.decrypt(k, iv, e.asString(), #ad); - - --[[ - 32 bit key - ]]-- - k = crypto.utils.hex.hexToString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); - ad = "additional data"; - - e = crypto.cipher.morus.encrypt(k, iv, m, ad); - results[3] = e.asHex(); - results[4] = crypto.cipher.morus.decrypt(k, iv, e.asString(), #ad); - - return table.concat(results, ", "); - `; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: data, - }; - - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results.join(', ')); - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/norx.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/norx.test.js deleted file mode 100644 index 473922a..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/cipher/norx.test.js +++ /dev/null @@ -1,69 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); - -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test('run norx cipher successfully', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - - const results = [ - // encrypted cipher - '0bb35a06938e6541eccd4440adb7b46118535f60b09b4adf378807a53df19fc4ea28', - // auth tag - '5a06938e6541eccd4440adb7b46118535f60b09b4adf378807a53df19fc4ea28', - // decrypted value - 'ao', - ]; - - const data = ` - local crypto = require(".crypto"); - local Hex = require(".crypto.util.hex") - - local results = {} - - -- nonce and key are 32 bytes each - local key = "super_duper_secret_password_shhh" - local nonce = "00000000000000000000000000000000" - - -- Data to encrypt - local data = "ao" - - -- Header and trailer are optional - local header, trailer = data, data - - local encrypted = crypto.cipher.norx.encrypt(key, nonce, data, header, trailer).asString() - local decrypted = crypto.cipher.norx.decrypt(key, nonce, encrypted, header, trailer) - - local authTag = encrypted:sub(#encrypted-32+1) - - results[1] = Hex.stringToHex(encrypted) - results[2] = Hex.stringToHex(authTag) - results[3] = decrypted - - return table.concat(results, ", ") - `; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: data, - }; - - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results.join(', ')); - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/blake2b.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/blake2b.test.js deleted file mode 100644 index b57df1e..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/blake2b.test.js +++ /dev/null @@ -1,50 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test('run sha3 hash successfully', async () => { - const results = [ - '576701fd79a126f2c414ef94adf1117c88943700f312679d018c29c378b2c807a3412b4e8d51e191c48fb5f5f54bf1bca29a714dda166797b3baf9ead862ae1d', - '7050811afc947ba7190bb3c0a7b79b4fba304a0de61d529c8a35bdcbbb5544f4', - '203c101980fdf6cf24d78879f2e3db86d73d91f7d60960b642022cd6f87408f8', - ]; - - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - - const data = ` - local crypto = require(".crypto"); - - local results = {}; - - results[1] = crypto.digest.blake2b("ao").asHex(); - results[2] = crypto.digest.blake2b("ao", 32).asHex(); - results[3] = crypto.digest.blake2b("ao", 32, "secret_key").asHex(); - - - return table.concat(results, ", "); - `; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: data, - }; - - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results.join(', ')); - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md2.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md2.test.js deleted file mode 100644 index ced036a..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md2.test.js +++ /dev/null @@ -1,55 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); - -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; - -test('run md2 hash successfully', async () => { - const cases = [ - ['', '8350e5a3e24c153df2275c9f80692773'], - ['ao', '0d4e80edd07bee6c7965b21b25a9b1ea'], - ['abc', 'da853b0d3f88d99b30283a69e6ded6bb'], - ['abcdefghijklmnopqrstuvwxyz', '4e8ddff3650292ab5a4108c3aa47940b'], - ['Hello World!', '315f7c67223f01fb7cab4b95100e872e'], - ]; - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const testCase = async (e) => { - const data = ` - local crypto = require(".crypto"); - - local str = crypto.utils.stream.fromString("${e[0]}"); - return crypto.digest.md2(str).asHex(); - `; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: data, - }; - - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, e[1]); - assert.ok(true); - }; - await testCase(cases[0]); - await testCase(cases[1]); - await testCase(cases[2]); - await testCase(cases[3]); - await testCase(cases[4]); - - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md4.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md4.test.js deleted file mode 100644 index a52bc31..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md4.test.js +++ /dev/null @@ -1,53 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); - -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test('run md4 hash successfully', async () => { - const cases = [ - ['', '31d6cfe0d16ae931b73c59d7e0c089c0'], - ['ao', 'e068dfe3d8cb95311b58be566db66954'], - ['abc', 'a448017aaf21d8525fc10ae87aa6729d'], - ['abcdefghijklmnopqrstuvwxyz', 'd79e1c308aa5bbcdeea8ed63df412da9'], - ['Hello World!', 'b2a5cc34fc21a764ae2fad94d56fadf6'], - ]; - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - - const testCase = async (e) => { - const data = ` - local crypto = require(".crypto"); - - local str = crypto.utils.stream.fromString("${e[0]}"); - return crypto.digest.md4(str).asHex(); - `; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: data, - }; - - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, e[1]); - assert.ok(true); - }; - await testCase(cases[0]); - await testCase(cases[1]); - await testCase(cases[2]); - await testCase(cases[3]); - await testCase(cases[4]); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md5.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md5.test.js deleted file mode 100644 index dbc8844..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/md5.test.js +++ /dev/null @@ -1,53 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; - -test('run md5 hash successfully', async () => { - const cases = [ - ['', 'd41d8cd98f00b204e9800998ecf8427e'], - ['ao', 'adac5e63f80f8629e9573527b25891d3'], - ['abc', '900150983cd24fb0d6963f7d28e17f72'], - ['abcdefghijklmnopqrstuvwxyz', 'c3fcd3d76192e4007dfb496cca67e13b'], - ['Hello World!', 'ed076287532e86365e841e92bfc50d8c'], - ]; - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - - const testCase = async (e) => { - const data = ` - local crypto = require(".crypto"); - - local str = crypto.utils.stream.fromString("${e[0]}"); - return crypto.digest.md5(str).asHex(); - `; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: data, - }; - - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, e[1]); - assert.ok(true); - }; - await testCase(cases[0]); - await testCase(cases[1]); - await testCase(cases[2]); - await testCase(cases[3]); - await testCase(cases[4]); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha1.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha1.test.js deleted file mode 100644 index a0236cb..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha1.test.js +++ /dev/null @@ -1,52 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test('run sha1 hash successfully', async () => { - const cases = [ - ['', 'da39a3ee5e6b4b0d3255bfef95601890afd80709'], - ['ao', 'c29dd6c83b67a1d6d3b28588a1f068b68689aa1d'], - ['abc', 'a9993e364706816aba3e25717850c26c9cd0d89d'], - ['abcdefghijklmnopqrstuvwxyz', '32d10c7b8cf96570ca04ce37f2a19d84240d3a89'], - ['Hello World!', '2ef7bde608ce5404e97d5f042f95f89f1c232871'], - ]; - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - - const testCase = async (e) => { - const data = ` - local crypto = require(".crypto"); - - local str = crypto.utils.stream.fromString("${e[0]}"); - return crypto.digest.sha1(str).asHex(); - `; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: data, - }; - - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, e[1]); - assert.ok(true); - }; - await testCase(cases[0]); - await testCase(cases[1]); - await testCase(cases[2]); - await testCase(cases[3]); - await testCase(cases[4]); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha2.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha2.test.js deleted file mode 100644 index 94f42f3..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha2.test.js +++ /dev/null @@ -1,50 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test('run sha2 hash successfully', async () => { - const results = [ - 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', - '6f36a696b17ce5a71efa700e8a7e47994f3e134a5e5f387b3e7c2c912abe94f94ee823f9b9dcae59af99e2e34c8b4fb0bd592260c6720ee49e5deaac2065c4b1', - ]; - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - - const data = ` - local crypto = require(".crypto"); - - local results = {}; - - local data1 = crypto.utils.stream.fromString("abc"); - local data2 = "ao" - - - results[1] = crypto.digest.sha2_256(data1).asHex(); - results[2] = crypto.digest.sha2_512(data2).asHex(); - - return table.concat(results, ", "); - `; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: data, - }; - - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results.join(', ')); - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha3.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha3.test.js deleted file mode 100644 index 0267adc..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/digest/sha3.test.js +++ /dev/null @@ -1,51 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test('run sha3 hash successfully', async () => { - const results = [ - '1bbe785577db997a394d5b4555eec9159cb51f235aec07514872d2d436c6e985', - '0c29f053400cb1764ce2ec555f598f497e6fcd1d304ce0125faa03bb724f63f213538f41103072ff62ddee701b52c73e621ed4d2254a3e5e9a803d83435b704d', - '76da52eec05b749b99d6e62bb52333c1569fe75284e6c82f3de12a4618be00d6', - '046fbfad009a12cef9ff00c2aac361d004347b2991c1fa80fba5582251b8e0be8def0283f45f020d4b04ff03ead9f6e7c43cc3920810c05b33b4873b99affdea', - ]; - - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - - const data = ` - local crypto = require(".crypto"); - - local results = {}; - - results[1] = crypto.digest.sha3_256("ao").asHex(); - results[2] = crypto.digest.sha3_512("ao").asHex(); - results[3] = crypto.digest.keccak256("ao").asHex(); - results[4] = crypto.digest.keccak512("ao").asHex(); - - return table.concat(results,", ") - `; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: data, - }; - - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results.join(', ')); - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/kdf/pbkdf2.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/kdf/pbkdf2.test.js deleted file mode 100644 index 455065d..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/kdf/pbkdf2.test.js +++ /dev/null @@ -1,48 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; - -test('run pbkdf2 successfully', async () => { - const results = ['C4C21BF2BBF61541408EC2A49C89B9C6']; - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - - const data = ` - local crypto = require(".crypto"); - - local results = {}; - - local salt = crypto.utils.array.fromString("salt") - local password = crypto.utils.array.fromString("password") - local iterations = 4 - local keyLen = 16 - - local out = crypto.kdf.pbkdf2(password, salt, iterations, keyLen) - - return out.asHex() - `; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: data, - }; - - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results); - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/mac/hmac.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/mac/hmac.test.js deleted file mode 100644 index 9f1e004..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/mac/hmac.test.js +++ /dev/null @@ -1,50 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test('run hmac successfully', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - - const results = [ - '3966f45acb53f7a1a493bae15afecb1a204fa32d', - '542da02a324155d688c7689669ff94c6a5f906892aa8eccd7284f210ac66e2a7', - ]; - - const data = ` - local crypto = require(".crypto") - - local data = crypto.utils.stream.fromString("ao") - local key = crypto.utils.array.fromString("super_secret_key") - - local results = {} - - results[1] = crypto.mac.createHmac(data, key).asHex() - results[2] = crypto.mac.createHmac(data, key, "sha256").asHex() - - return table.concat(results, ", ") - `; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: data, - }; - - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, results.join(', ')); - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/random.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/random.test.js deleted file mode 100644 index 59c0302..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/crypto/random.test.js +++ /dev/null @@ -1,37 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test('run random generator successfully', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - - const data = ` - local crypto = require(".crypto") - - return crypto.random(); - `; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: data, - }; - - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, 532713800); - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/eval.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/eval.test.js deleted file mode 100644 index b9eae61..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/eval.test.js +++ /dev/null @@ -1,107 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; -test('run evaluate action unsuccessfully', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: '100 < undefined', - }; - const result = await handle(null, msg, env); - - assert.ok(result.Error.includes('attempt to compare number with nil')); - assert.ok(true); -}); - -test('run evaluate action successfully', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: '1 + 1', - }; - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, '2'); - assert.ok(true); -}); - -test('print hello world', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: `print("Hello World")`, - }; - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, 'Hello World'); - assert.ok(true); -}); - -test('create an Assignment', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: 'Assign({ Processes = { "pid-1", "pid-2" }, Message = "mid-1" })', - }; - const result = await handle(null, msg, env); - - assert.deepStrictEqual(result.Assignments, [ - { Processes: ['pid-1', 'pid-2'], Message: 'mid-1' }, - ]); - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/handlers.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/handlers.test.js deleted file mode 100644 index 8f1c97f..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/handlers.test.js +++ /dev/null @@ -1,332 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; - -test('handlers receive', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: ` -local msg = ao.send({Target = ao.id, Data = "Hello"}) -local res = Handlers.receive({From = msg.Target, ['X-Reference'] = msg.Ref_}) -print('received msg') -return require('json').encode(res) - `, - }; - - // load handler - const { Memory, Output, Messages } = await handle(null, msg, env); - //console.log(Output) - console.log(Messages[0]); - // --- - const m = { - Target: 'AOS', - From: 'FRED', - Owner: 'FRED', - Tags: [ - { - name: 'X-Reference', - value: '1', - }, - ], - Data: 'test receive', - }; - const result = await handle(Memory, m, env); - console.log(result.Output); - assert.ok(true); -}); - -test('resolvers', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: ` -Handlers.once("onetime", - { - Action = "ping", - Data = "ping" - }, - function (Msg) - print("pong") - end -) - `, - }; - // load handler - const { Memory } = await handle(null, msg, env); - // --- - const ping = { - Target: 'AOS', - From: 'FRED', - Owner: 'FRED', - Tags: [{ name: 'Action', value: 'ping' }], - Data: 'ping', - }; - const result = await handle(Memory, ping, env); - // handled once - assert.equal(result.Output.data, 'pong'); -}); - -test('handlers once', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: ` -Handlers.once("onetime", - Handlers.utils.hasMatchingData("ping"), - function (Msg) - print("pong") - end -) - `, - }; - // load handler - const { Memory } = await handle(null, msg, env); - // --- - const ping = { - From: 'FRED', - Target: 'AOS', - Owner: 'FRED', - Tags: [], - Data: 'ping', - }; - const result = await handle(Memory, ping, env); - // handled once - assert.equal(result.Output.data, 'pong'); - - const result2 = await handle(result.Memory, ping, env); - // not handled - assert.ok(result2.Output.data.includes('New Message From')); -}); - -test('ping pong', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: ` -Handlers.add("ping", - Handlers.utils.hasMatchingData("ping"), - function (Msg) - print("pong") - end -) - `, - }; - // load handler - const { Memory } = await handle(null, msg, env); - // --- - const ping = { - Target: 'AOS', - From: 'FRED', - Owner: 'FRED', - Tags: [], - Data: 'ping', - }; - const result = await handle(Memory, ping, env); - assert.equal(result.Output.data, 'pong'); - assert.ok(true); -}); - -test('handler pipeline', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: ` -Handlers.add("one", - function (Msg) - return "continue" - end, - function (Msg) - print("one") - end -) -Handlers.add("two", - function (Msg) - return "continue" - end, - function (Msg) - print("two") - end -) - -Handlers.add("three", - function (Msg) - return "skip" - end, - function (Msg) - print("three") - end -) - `, - }; - // load handler - const { Memory } = await handle(null, msg, env); - // --- - const ping = { - Target: 'AOS', - From: 'FRED', - Owner: 'FRED', - Tags: [], - Data: 'ping', - }; - const result = await handle(Memory, ping, env); - assert.equal( - result.Output.data, - 'one\ntwo\n\x1B[90mNew Message From \x1B[32mFRE...RED\x1B[90m: \x1B[90mData = \x1B[34mping\x1B[0m', - ); - assert.ok(true); -}); - -test('timestamp', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: ` -Handlers.add("timestamp", - Handlers.utils.hasMatchingData("timestamp"), - function (Msg) - print(os.time()) - end -) - `, - }; - // load handler - const { Memory } = await handle(null, msg, env); - // --- - const currentTimestamp = Date.now(); - const timestamp = { - Target: 'AOS', - From: 'FRED', - Owner: 'FRED', - Tags: [], - Data: 'timestamp', - Timestamp: currentTimestamp, - }; - const result = await handle(Memory, timestamp, env); - assert.equal(result.Output.data, currentTimestamp); - assert.ok(true); -}); - -test('test pattern, fn handler', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: ` -Handlers.add("Balance", - function (msg) - msg.reply({Data = "1000"}) - end -) - `, - }; - // load handler - const { Memory } = await handle(null, msg, env); - // --- - const currentTimestamp = Date.now(); - const balance = { - Target: 'AOS', - From: 'FRED', - Owner: 'FRED', - Tags: [{ name: 'Action', value: 'Balance' }], - Data: 'timestamp', - Timestamp: currentTimestamp, - }; - const result = await handle(Memory, balance, env); - assert.equal(result.Messages[0].Data, '1000'); - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/inbox.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/inbox.test.js deleted file mode 100644 index ff72b4b..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/inbox.test.js +++ /dev/null @@ -1,51 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; - -test.skip('inbox unbounded', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Data: 'Hello', - Tags: [], - }; - const result = await handle(null, msg, env); - let memory = result.Memory; - for (var i = 0; i < 10001; i++) { - const { Memory } = await handle(memory, msg, env); - memory = Memory; - } - const count = await handle( - memory, - { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: '#Inbox', - }, - env, - ); - //assert.equal(count.Error, 'Error') - assert.equal(count.Output?.data, '10000'); - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/magic-table.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/magic-table.test.js deleted file mode 100644 index 827d2fd..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/magic-table.test.js +++ /dev/null @@ -1,65 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; - -test('magictable to wrap send to convert data to json', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: 'Send({ Target = "AOS", Data = { foo = "bar" }})', - }; - const result = await handle(null, msg, env); - assert.equal(result.Messages[0].Data, '{"foo":"bar"}'); - const msg2 = Object.assign({}, msg, result.Messages[0]); - const tableResult = await handle(result.Memory, msg2, env); - const inboxResult = await handle( - tableResult.Memory, - Object.assign({}, msg, { - Tags: [{ name: 'Action', value: 'Eval' }], - Data: 'Inbox[1].Data.foo', - }), - env, - ); - assert.equal(inboxResult.Output.data, 'bar'); -}); - -test('magictable to wrap swap to convert data to json', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: 'Spawn("AWESOME_SAUCE", { Target = "TEST", Data = { foo = "bar" }})', - }; - const result = await handle(null, msg, env); - assert.equal(result.Spawns[0].Data, '{"foo":"bar"}'); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/print.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/print.test.js deleted file mode 100644 index 219e9f0..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/print.test.js +++ /dev/null @@ -1,124 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; - -test('multi print feature', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: ` -print("one") -print("two") -`, - }; - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, 'one\ntwo'); - assert.ok(true); -}); - -test('multi print feature via handler', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: 'Handlers.add("ping", Handlers.utils.hasMatchingData("ping"), function (m) print(m.Data); print("pong") end)', - }; - const { Memory } = await handle(null, msg, env); - let msg2 = msg; - msg2.Tags = []; - msg2.Data = 'ping'; - const result = await handle(Memory, msg2, env); - assert.equal(result.Output.data, 'ping\npong'); - assert.ok(true); -}); - -test('Typos for functions should generate errors', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: 'Handers.add("ping", Handlers.utils.hasMatchingData("ping"), function (m) print(m.Data); print("pong") end)', - }; - const { Memory, Output, Error } = await handle(null, msg, env); - - let msg2 = msg; - msg2.Tags = [{ name: 'Action', value: 'Eval' }]; - msg2.Data = 'Errors'; - const result = await handle(Memory, msg2, env); - assert.ok( - result.Output.data.includes( - "attempt to index a nil value (global 'Handers')", - ), - ); -}); - -test('Print Errors in Handlers', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: 'Handlers.add("ping", Handlers.utils.hasMatchingData("ping"), function (m) print(m.Data); print("pong" .. x) end)', - }; - const { Memory, Output, Error } = await handle(null, msg, env); - - let msg2 = msg; - msg2.Tags = []; - msg2.Data = 'ping'; - const result = await handle(Memory, msg2, env); - - assert.ok(result.Output.data.includes('handling message')); - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/random.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/random.test.js deleted file mode 100644 index 6f489e7..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/random.test.js +++ /dev/null @@ -1,31 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; - -test('generate random number', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: 'math.random(10)', - }; - const result = await handle(null, msg, env); - assert.equal(result.Output?.data, '9'); - assert.ok(true); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/state.test.js b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/state.test.js deleted file mode 100644 index 3d29cfd..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/test/state.test.js +++ /dev/null @@ -1,63 +0,0 @@ -import AoLoader from '@permaweb/ao-loader'; -import fs from 'fs'; -import * as assert from 'node:assert'; -import { test } from 'node:test'; - -const wasm = fs.readFileSync('./process.wasm'); -const options = { format: 'wasm64-unknown-emscripten-draft_2024_02_15' }; - -test('check state properties for aos', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [{ name: 'Name', value: 'Thomas' }], - }, - }; - const msg = { - Target: 'AOS', - From: 'FOOBAR', - Owner: 'FOOBAR', - From: 'FOOBAR', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: 'print("name: " .. Name .. ", owner: " .. Owner)', - }; - const result = await handle(null, msg, env); - - assert.equal(result.Output?.data, 'name: Thomas, owner: FOOBAR'); - assert.ok(true); -}); - -test('test authorities', async () => { - const handle = await AoLoader(wasm, options); - const env = { - Process: { - Id: 'AOS', - Owner: 'FOOBAR', - Tags: [ - { name: 'Name', value: 'Thomas' }, - { name: 'Authority', value: 'BOOP' }, - ], - }, - }; - const msg = { - Target: 'AOS', - Owner: 'BEEP', - From: 'BAM', - ['Block-Height']: '1000', - Id: '1234xyxfoo', - Module: 'WOOPAWOOPA', - Tags: [{ name: 'Action', value: 'Eval' }], - Data: '1 + 1', - }; - const result = await handle(null, msg, env); - assert.ok( - result.Output.data.includes( - 'Message is not trusted! From: BAM - Owner: BEEP', - ), - ); -}); diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.lua b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.lua deleted file mode 100644 index 8f30cde..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.lua +++ /dev/null @@ -1,261 +0,0 @@ -local utils = { _version = "0.0.5" } - -function utils.matchesPattern(pattern, value, msg) - -- If the key is not in the message, then it does not match - if(not pattern) then - return false - end - -- if the patternMatchSpec is a wildcard, then it always matches - if pattern == '_' then - return true - end - -- if the patternMatchSpec is a function, then it is executed on the tag value - if type(pattern) == "function" then - if pattern(value, msg) then - return true - else - return false - end - end - - -- if the patternMatchSpec is a string, check it for special symbols (less `-` alone) - -- and exact string match mode - if (type(pattern) == 'string') then - if string.match(pattern, "[%^%$%(%)%%%.%[%]%*%+%?]") then - if string.match(value, pattern) then - return true - end - else - if value == pattern then - return true - end - end - end - - -- if the pattern is a table, recursively check if any of its sub-patterns match - if type(pattern) == 'table' then - for _, subPattern in pairs(pattern) do - if utils.matchesPattern(subPattern, value, msg) then - return true - end - end - end - - return false -end - -function utils.matchesSpec(msg, spec) - if type(spec) == 'function' then - return spec(msg) - -- If the spec is a table, step through every key/value pair in the pattern and check if the msg matches - -- Supported pattern types: - -- - Exact string match - -- - Lua gmatch string - -- - '_' (wildcard: Message has tag, but can be any value) - -- - Function execution on the tag, optionally using the msg as the second argument - -- - Table of patterns, where ANY of the sub-patterns matching the tag will result in a match - end - if type(spec) == 'table' then - for key, pattern in pairs(spec) do - if not msg[key] then - return false - end - if not utils.matchesPattern(pattern, msg[key], msg) then - return false - end - end - return true - end - if type(spec) == 'string' and msg.Action and msg.Action == spec then - return true - end - return false -end - -local function isArray(table) - if type(table) == "table" then - local maxIndex = 0 - for k, v in pairs(table) do - if type(k) ~= "number" or k < 1 or math.floor(k) ~= k then - return false -- If there's a non-integer key, it's not an array - end - maxIndex = math.max(maxIndex, k) - end - -- If the highest numeric index is equal to the number of elements, it's an array - return maxIndex == #table - end - return false -end - --- @param {function} fn --- @param {number} arity -utils.curry = function (fn, arity) - assert(type(fn) == "function", "function is required as first argument") - arity = arity or debug.getinfo(fn, "u").nparams - if arity < 2 then return fn end - - return function (...) - local args = {...} - - if #args >= arity then - return fn(table.unpack(args)) - else - return utils.curry(function (...) - return fn(table.unpack(args), ...) - end, arity - #args) - end - end -end - ---- Concat two Array Tables. --- @param {table} a --- @param {table} b -utils.concat = utils.curry(function (a, b) - assert(type(a) == "table", "first argument should be a table that is an array") - assert(type(b) == "table", "second argument should be a table that is an array") - assert(isArray(a), "first argument should be a table") - assert(isArray(b), "second argument should be a table") - - local result = {} - for i = 1, #a do - result[#result + 1] = a[i] - end - for i = 1, #b do - result[#result + 1] = b[i] - end - return result - --return table.concat(a,b) -end, 2) - ---- reduce applies a function to a table --- @param {function} fn --- @param {any} initial --- @param {table} t -utils.reduce = utils.curry(function (fn, initial, t) - assert(type(fn) == "function", "first argument should be a function that accepts (result, value, key)") - assert(type(t) == "table" and isArray(t), "third argument should be a table that is an array") - local result = initial - for k, v in pairs(t) do - if result == nil then - result = v - else - result = fn(result, v, k) - end - end - return result -end, 3) - --- @param {function} fn --- @param {table} data -utils.map = utils.curry(function (fn, data) - assert(type(fn) == "function", "first argument should be a unary function") - assert(type(data) == "table" and isArray(data), "second argument should be an Array") - - local function map (result, v, k) - result[k] = fn(v, k) - return result - end - - return utils.reduce(map, {}, data) -end, 2) - --- @param {function} fn --- @param {table} data -utils.filter = utils.curry(function (fn, data) - assert(type(fn) == "function", "first argument should be a unary function") - assert(type(data) == "table" and isArray(data), "second argument should be an Array") - - local function filter (result, v, _k) - if fn(v) then - table.insert(result, v) - end - return result - end - - return utils.reduce(filter,{}, data) -end, 2) - --- @param {function} fn --- @param {table} t -utils.find = utils.curry(function (fn, t) - assert(type(fn) == "function", "first argument should be a unary function") - assert(type(t) == "table", "second argument should be a table that is an array") - for _, v in pairs(t) do - if fn(v) then - return v - end - end -end, 2) - --- @param {string} propName --- @param {string} value --- @param {table} object -utils.propEq = utils.curry(function (propName, value, object) - assert(type(propName) == "string", "first argument should be a string") - -- assert(type(value) == "string", "second argument should be a string") - assert(type(object) == "table", "third argument should be a table") - - return object[propName] == value -end, 3) - --- @param {table} data -utils.reverse = function (data) - assert(type(data) == "table", "argument needs to be a table that is an array") - return utils.reduce( - function (result, v, i) - result[#data - i + 1] = v - return result - end, - {}, - data - ) -end - --- @param {function} ... -utils.compose = utils.curry(function (...) - local mutations = utils.reverse({...}) - - return function (v) - local result = v - for _, fn in pairs(mutations) do - assert(type(fn) == "function", "each argument needs to be a function") - result = fn(result) - end - return result - end -end, 2) - --- @param {string} propName --- @param {table} object -utils.prop = utils.curry(function (propName, object) - return object[propName] -end, 2) - --- @param {any} val --- @param {table} t -utils.includes = utils.curry(function (val, t) - assert(type(t) == "table", "argument needs to be a table") - return utils.find(function (v) return v == val end, t) ~= nil -end, 2) - --- @param {table} t -utils.keys = function (t) - assert(type(t) == "table", "argument needs to be a table") - local keys = {} - for key in pairs(t) do - table.insert(keys, key) - end - return keys -end - --- @param {table} t -utils.values = function (t) - assert(type(t) == "table", "argument needs to be a table") - local values = {} - for _, value in pairs(t) do - table.insert(values, value) - end - return values -end - -return utils diff --git a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.md b/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.md deleted file mode 100644 index 565c427..0000000 --- a/tools/working-af96ce24-9a4d-47fb-b0dc-c879d402fd9d/aos-process/utils.md +++ /dev/null @@ -1,89 +0,0 @@ -# Lua Utils Module Documentation - -## Module Overview - -The Lua Utils module provides a collection of utility functions for functional programming in Lua. It includes functions for array manipulation such as concatenation, mapping, reduction, filtering, and finding elements, as well as a property equality checker. - -## Module Functions - -### 1. `concat` - -Concatenates two arrays. - -- **Syntax:** `utils.concat(a)(b)` -- **Parameters:** - - `a` (table): The first array. - - `b` (table): The second array. -- **Returns:** A new array containing all elements from `a` followed by all elements from `b`. -- **Example:** `utils.concat({1, 2})({3, 4}) -- returns {1, 2, 3, 4}` - -### 2. `map` - -Applies a function to each element of an array. - -- **Syntax:** `utils.map(fn)(t)` -- **Parameters:** - - `fn` (function): A function to apply to each element. - - `t` (table): The array to map over. -- **Returns:** A new array with each element being the result of applying `fn`. -- **Example:** `utils.map(function(x) return x * 2 end)({1, 2, 3}) -- returns {2, 4, 6}` - -### 3. `reduce` - -Reduces an array to a single value by iteratively applying a function. - -- **Syntax:** `utils.reduce(fn)(initial)(t)` -- **Parameters:** - - `fn` (function): A function to apply. - - `initial`: Initial accumulator value. - - `t` (table): The array to reduce. -- **Returns:** The final accumulated value. -- **Example:** `utils.reduce(function(acc, x) return acc + x end)(0)({1, 2, 3}) -- returns 6` - -### 4. `filter` - -Filters an array based on a predicate function. - -- **Syntax:** `utils.filter(fn)(t)` -- **Parameters:** - - `fn` (function): A predicate function to determine if an element should be included. - - `t` (table): The array to filter. -- **Returns:** A new array containing only elements that satisfy `fn`. -- **Example:** `utils.filter(function(x) return x > 1 end)({1, 2, 3}) -- returns {2, 3}` - -### 5. `find` - -Finds the first element in an array that satisfies a predicate function. - -- **Syntax:** `utils.find(fn)(t)` -- **Parameters:** - - `fn` (function): A predicate function. - - `t` (table): The array to search. -- **Returns:** The first element that satisfies `fn`, or `nil` if none do. -- **Example:** `utils.find(function(x) return x > 1 end)({1, 2, 3}) -- returns 2` - -### 6. `propEq` - -Checks if a specified property of an object equals a given value. - -- **Syntax:** `utils.propEq(propName)(value)(object)` -- **Parameters:** - - `propName` (string): The name of the property. - - `value` (string): The value to compare against. - - `object` (table): The object to check. -- **Returns:** `true` if `object[propName]` equals `value`, otherwise `false`. -- **Example:** `utils.propEq("name")("Lua")({name = "Lua"}) -- returns true` - -## Version - -- The module is currently at version 0.0.1. - -## Notes - -- This module is designed for functional programming style in Lua. -- It's important to ensure that inputs to these functions are of correct types as expected by each function. -- The module does not modify the original arrays but returns new arrays or values. - ---- - -This documentation provides a basic overview and examples for each function in the Utils module. Users should adapt the examples to their specific use cases. diff --git a/yarn.lock b/yarn.lock index f9d129d..e7a56cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3606,6 +3606,13 @@ resolved "https://registry.yarnpkg.com/@permaweb/ao-loader/-/ao-loader-0.0.36.tgz#901632148d7c8ee6b5013068b0249d047e9d9d43" integrity sha512-So4WzXjh+Ovx5aAivw3JIL2bBgqDsetu6+Dyn4S23HeOyZTS9Sm2MJvTrnt3LvjdpsYSxfp2p445L1uEOspMTA== +"@permaweb/ao-loader@^0.0.43": + version "0.0.43" + resolved "https://registry.yarnpkg.com/@permaweb/ao-loader/-/ao-loader-0.0.43.tgz#bf980be06ec397ad475a023550b5526bf53e2a9b" + integrity sha512-xPYzyKSCqtL0U8oUcCrW+uPpm7IcMncM5IPVGCGKljxA3IQA/HI8S5XA6tcZUaDRCl8VSVsJzqOgkdzy1JGi5w== + dependencies: + "@permaweb/wasm-metering" "^0.2.2" + "@permaweb/ao-scheduler-utils@~0.0.20", "@permaweb/ao-scheduler-utils@~0.0.23": version "0.0.24" resolved "https://registry.yarnpkg.com/@permaweb/ao-scheduler-utils/-/ao-scheduler-utils-0.0.24.tgz#a7d2c1e09f9b6ea5d45127fa395bbbcef6688452" @@ -3643,6 +3650,23 @@ warp-arbundles "^1.0.4" zod "^3.23.8" +"@permaweb/wasm-json-toolkit@^0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@permaweb/wasm-json-toolkit/-/wasm-json-toolkit-0.2.9.tgz#241cdf37c1690a751a73dd05987336142e5576d1" + integrity sha512-CGCeUwS+UeqUdvORiyG0LykkQXLTwS5TWc590CUkDfOYyBUSPv8pse0sJStvTC9LKAzuNx3ELBvmqHCI4muUAA== + dependencies: + buffer-pipe "0.0.3" + leb128 "0.0.4" + safe-buffer "^5.1.2" + +"@permaweb/wasm-metering@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@permaweb/wasm-metering/-/wasm-metering-0.2.2.tgz#a854c485d9ddbbefb4a17a3692822b696d54a2c7" + integrity sha512-xM2MbPkHc4rzhTR6VH5eXtfC+liaYSuNCa0kPRaqSZO2gr1SirJWnzUBDa5VOfTBTgIlIVv5RW+Mkbt/VuK+oA== + dependencies: + "@permaweb/wasm-json-toolkit" "^0.2.9" + leb128 "^0.0.4" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -5771,7 +5795,7 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.8, bn.js@^4.11.9: +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== @@ -5942,6 +5966,20 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-pipe@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/buffer-pipe/-/buffer-pipe-0.0.0.tgz#186ec257d696e8e74c3051160a0e9e9a9811a387" + integrity sha512-PvKbsvQOH4dcUyUEvQQSs3CIkkuPcOHt3gKnXwf4HsPKFDxSN7bkmICVIWgOmW/jx/fAEGGn4mIayIJPLs7G8g== + dependencies: + safe-buffer "^5.1.1" + +buffer-pipe@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/buffer-pipe/-/buffer-pipe-0.0.3.tgz#242197681d4591e7feda213336af6c07a5ce2409" + integrity sha512-GlxfuD/NrKvCNs0Ut+7b1IHjylfdegMBxQIlZHj7bObKVQBxB5S84gtm2yu1mQ8/sSggceWBDPY0cPXgvX2MuA== + dependencies: + safe-buffer "^5.1.2" + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -9398,6 +9436,14 @@ language-tags@^1.0.9: dependencies: language-subtag-registry "^0.3.20" +leb128@0.0.4, leb128@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/leb128/-/leb128-0.0.4.tgz#f96d698cf3ba5b677423abfe50b7e9b2df1463ff" + integrity sha512-2zejk0fCIgY8RVcc/KzvyfpDio5Oo8HgPZmkrOmdwmbk0KpKpgD+JKwikxKk8cZYkANIhwHK50SNukkCm3XkCQ== + dependencies: + bn.js "^4.11.6" + buffer-pipe "0.0.0" + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" From a8b079f3ab61a598024535ff087e89d333db4bcc Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Thu, 31 Oct 2024 18:53:24 -0600 Subject: [PATCH 7/9] fix(git): add dist to git-ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c75755b..ff666bf 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ pnpm-debug.log* lerna-debug.log* node_modules +dist dist-ssr dev-dist *.local From 845f3518624b57f2ff44b8b613cf6f4d9934f07b Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Sat, 2 Nov 2024 12:14:27 -0600 Subject: [PATCH 8/9] fix(processes): update the process code to abstract kv stores --- .vscode/settings.json | 8 +- diagrams/process_interactions.drawio | 118 ++++++ processes/acl.lua | 101 +++++ processes/diagrams/kv-topology.drawio | 55 +++ processes/kv_registry.lua | 91 +++++ processes/kv_store.lua | 242 +++++++++++ .../{vaultString.lua => kv_store_string.lua} | 4 +- processes/registry.lua | 123 ------ .../tools}/build-aos-flavor.js | 0 {tools => processes/tools}/bundle-aos.js | 0 {tools => processes/tools}/constants.js | 0 ...JDuhwGUsFca1rgfE7ehAKSdgOqPj6aYYy5u4s.wasm | Bin ...rBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk.wasm | Bin {tools => processes/tools}/lua-bundler.js | 0 {tools => processes/tools}/publish-aos.js | 0 .../tools}/scripts/install-deps.js | 0 {tools => processes/tools}/spawn-aos.js | 0 {tools => processes/tools}/utils.js | 0 processes/utils.lua | 381 ++++++++++++++++++ processes/vault.lua | 186 --------- src/utils/vault.ts | 2 +- 21 files changed, 998 insertions(+), 313 deletions(-) create mode 100644 diagrams/process_interactions.drawio create mode 100644 processes/acl.lua create mode 100644 processes/diagrams/kv-topology.drawio create mode 100644 processes/kv_registry.lua create mode 100644 processes/kv_store.lua rename processes/{vaultString.lua => kv_store_string.lua} (99%) delete mode 100644 processes/registry.lua rename {tools => processes/tools}/build-aos-flavor.js (100%) rename {tools => processes/tools}/bundle-aos.js (100%) rename {tools => processes/tools}/constants.js (100%) rename {tools => processes/tools}/fixtures/aos-C61NgrJDuhwGUsFca1rgfE7ehAKSdgOqPj6aYYy5u4s.wasm (100%) rename {tools => processes/tools}/fixtures/aos-cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk.wasm (100%) rename {tools => processes/tools}/lua-bundler.js (100%) rename {tools => processes/tools}/publish-aos.js (100%) rename {tools => processes/tools}/scripts/install-deps.js (100%) rename {tools => processes/tools}/spawn-aos.js (100%) rename {tools => processes/tools}/utils.js (100%) create mode 100644 processes/utils.lua delete mode 100644 processes/vault.lua diff --git a/.vscode/settings.json b/.vscode/settings.json index a523b64..57dd13d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,5 +24,11 @@ "undernames", "Undernames", "UNDERNAMES" - ] + ], + "Lua.workspace.library": ["${addons}/ao/module/library"], + "Lua.workspace.checkThirdParty": true, + "[lua]": { + "editor.defaultFormatter": "JohnnyMorganz.stylua", + "editor.formatOnSave": true + } } diff --git a/diagrams/process_interactions.drawio b/diagrams/process_interactions.drawio new file mode 100644 index 0000000..82dd7cf --- /dev/null +++ b/diagrams/process_interactions.drawio @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/processes/acl.lua b/processes/acl.lua new file mode 100644 index 0000000..69da584 --- /dev/null +++ b/processes/acl.lua @@ -0,0 +1,101 @@ +--- ACL module for role-based and path-specific access control. +-- This module allows defining roles, assigning roles to users, checking permissions, +-- and handling authorization requests with support for path-based permissions. +-- @module acl + +local acl = {} + +--- Table of defined roles and their permissions. +-- Each role has an associated table of permissions, where permissions may be path-specific. +acl.roles = {} + +--- Table of users and their assigned roles. +-- Each user has an associated table of roles they belong to. +acl.users = {} + +--- Table of pending authorization requests by user. +-- Each user can have one pending authorization request at a time. +acl.authorizationRequests = {} -- Store pending authorization requests by user + +--- Define a new role with a set of permissions. +---@param role string The name of the role to define. +---@param permissions table A table of permissions associated with the role. +-- Each permission can either be a boolean (true for global access on that action) or a table of path patterns. +-- For path-specific permissions, use a table with patterns that define the paths on which the role has access. +-- For example, a role with `{ ["KV-Store.Set"] = { "data.entries", "config.settings" } }` can only modify these paths. +function acl.defineRole(role, permissions) + acl.roles[role] = permissions or {} +end + +--- Assign a role to a user. +---@param user string The identifier for the user. +---@param role string The role to assign to the user. +---@raise Error if the role is not defined. +function acl.assignRole(user, role) + if not acl.roles[role] then + error("Role '" .. role .. "' is not defined") + end + acl.users[user] = acl.users[user] or {} + acl.users[user][role] = true +end + +--- Check if a user has permission for a specific action and path. +---@param user string The identifier for the user. +---@param action string The action the user wishes to perform. +---@param path string The specific path being accessed or modified. +---@return boolean True if the user has permission for the action and path, otherwise false. +---@details Each action’s permission can be `true` for global access or a table of paths for restricted access. +-- For example, `permissions = { ["KV-Store.Set"] = { "config.settings", "data.entries" } }` +-- allows modifying only `config.settings` and `data.entries`. +function acl.hasPermission(user, action, path) + local userRoles = acl.users[user] + if not userRoles then + return false + end + + for role in pairs(userRoles) do + local rolePermissions = acl.roles[role] + if rolePermissions then + local actionPermissions = rolePermissions[action] + if actionPermissions then + -- Global permission if set to `true` + if actionPermissions == true then + return true + end + -- If actionPermissions is a table, treat each entry as a path pattern + if type(actionPermissions) == "table" then + for _, pattern in ipairs(actionPermissions) do + if path:match(pattern) then + return true -- Path matches, permission granted + end + end + end + end + end + end + return false -- No matching permissions found +end + +--- Store a user's authorization request. +-- This replaces any existing request for the user. +---@param user string The identifier for the user. +---@param role string The role the user is requesting. +---@param permissions table A table of actions and corresponding permissions being requested. +function acl.storeAuthorizationRequest(user, role, permissions) + acl.authorizationRequests[user] = { role = role, permissions = permissions } +end + +--- Retrieve a user's current authorization request. +---@param user string The identifier for the user. +---@return table The user's authorization request, containing the requested role, paths, and permissions, or nil if no request exists. +function acl.getAuthorizationRequest(user) + return acl.authorizationRequests[user] +end + +--- Clear a user's authorization request after processing. +---@param user string The identifier for the user. +function acl.clearAuthorizationRequest(user) + acl.authorizationRequests[user] = nil +end + +return acl diff --git a/processes/diagrams/kv-topology.drawio b/processes/diagrams/kv-topology.drawio new file mode 100644 index 0000000..642eaff --- /dev/null +++ b/processes/diagrams/kv-topology.drawio @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/processes/kv_registry.lua b/processes/kv_registry.lua new file mode 100644 index 0000000..4987aab --- /dev/null +++ b/processes/kv_registry.lua @@ -0,0 +1,91 @@ +local kvRegistry = package.loaded["kv_registry"] or {} + +kvRegistry.ActionMap = { + spawnKVStore = "KV-Registry.Spawn-KV-Store", + getKVStores = "KV-Registry.Get-KV-Stores", + -- external action defined in the vault or "remote kv-process store" + subscriberNotice = "KV-Store.Subscriber-Notice", +} + +--[[ + UserList = { + [address] = { [storeid2] = true } + } + ]] +kvRegistry.UserList = kvRegistry.UserList or {} +--[[ + KVStoreList = { + [storeId] = { + Owner = address, + Controllers = {address1 = true}, + } + } + ]] +kvRegistry.KVStoreList = kvRegistry.KVStoreList or {} + +function kvRegistry.init() + local json = require("json") + local utils = require(".utils") + local KVStoreCode = require(".kv_store_string") + + local ActionMap = kvRegistry.ActionMap + local UserList = kvRegistry.UserList + local KVStoreList = kvRegistry.KVStoreList + local defaultKvStoreName = "Key_Value_Store" + + utils.createActionHandler(ActionMap.spawnKVProcess, function(msg) + -- initialize UserList[msg.From] if it doesn't exist + UserList[msg.From] = UserList[msg.From] or { + Owned = {}, + Controlled = {}, + } + local kvStoreSpawnMsg = Spawn(ao.env.Module.Id, { + Tags = { + ["KV-Registry"] = ao.id, + ["KV-Store-Name"] = msg["KV-Store-Name"] or defaultKvStoreName, + Creator = msg.From, + Authority = ao.authorities[1], + ["X-Original-Msg-Id"] = msg.Id, + ["On-Boot"] = "Data", + }, + Data = KVStoreCode, + }).receive({ Action = "Spawned", ["X-Original-Msg-Id"] = msg.Id }) + + UserList[msg.From].Owned[kvStoreSpawnMsg.Process] = true + + KVStoreList[kvStoreSpawnMsg.Process] = { + Owner = msg.From, + Controllers = {}, + } + + msg.reply({ + Action = ActionMap.spawnKVProcess .. "-Notice", + ["KV-Store-Id"] = kvStoreSpawnMsg.Process, + Data = kvStoreSpawnMsg.Process, + }) + end) + + utils.createActionHandler(ActionMap.subscriberNotice, function(msg) + assert(KVStoreList[msg.From], "KV Store not found") + + local store = json.decode(msg.Data) + assert(store.Owner, "Owner is required in notice") + assert(store.Controllers, "Controllers is required in notice") + utils.updateAffiliations(msg.From, store, UserList, KVStoreList) + end) + + utils.createActionHandler(ActionMap.getKVStores, function(msg) + local address = msg.Tags["Address"] + assert(type(address) == "string", "Address is required") + + -- Send the affiliations table + ao.send({ + Target = msg.From, + Action = ActionMap.getKVStores .. "-Notice", + ["Message-Id"] = msg.Id, + Data = json.encode(utils.affiliationsForAddress(address, KVStoreList)), + }) + end) +end + +return kvRegistry diff --git a/processes/kv_store.lua b/processes/kv_store.lua new file mode 100644 index 0000000..1a5282d --- /dev/null +++ b/processes/kv_store.lua @@ -0,0 +1,242 @@ +local json = require("json") +local utils = require(".utils") +local acl = require(".acl") + +local kv_store = {} + +kv_store.ActionMap = { + info = "Info", + set = "KV-Store.Set", + get = "KV-Store.Get", + setControllers = "KV-Store.Set-Controllers", + setSubscribers = "KV-Store.Set-Subscribers", + -- ACL + requestAuthorization = "KV-Store.Request-Authorization", + --[[ example secret configuration path. This would allow the collaborator to set their config. + Once they have the access role they can set their config and secretorium can use that to set the shares. + { + secretName = { + collaborators = { + ["id"] = { + encryptionPublicKey = "publicKey", + chainConfig = {...config} + } + } + } + } + ]] + getAuthorizationRequests = "KV-Store.Get-Authorization-Requests", + authorize = "KV-Store.Authorize", + accessControlList = "KV-Store.Access-Control-List", +} + +Owner = ao.env.Process.Tags["Creator"] or Owner + +State = State or { + name = ao.env.Process.Tags["KV-Store-Name"], +} +Subscribers = Subscribers or { ao.env.Process.Tags["KV-Registry"] } +Controllers = Controllers or { Owner } + +function kv_store.authorized(From) + if From == Owner then + return true + end + for _, value in ipairs(Controllers) do + if value == From then + return true + end + end + return false +end + +function kv_store.setNestedValue(tbl, path, value) + local current = tbl + for segment in string.gmatch(path, "[^%.]+") do + -- Create the nested table if it doesn't exist + if not current[segment] then + current[segment] = {} + end + -- Move down the path unless we're at the final segment + if segment == string.match(path, "[^%.]+$") then + current[segment] = value + else + current = current[segment] + end + end +end + +function kv_store.getStateValue(path) + local current = utils.deepCopy(State) + for segment in string.gmatch(path, "[^%.]+") do + -- Check if the segment exists in the current table + if current[segment] == nil then + return nil -- Return nil if any part of the path is missing + end + current = current[segment] + end + return current +end + +function kv_store.init() + local ActionMap = kv_store.ActionMap + + acl.defineRole("admin", { + [ActionMap.set] = true, + [ActionMap.setControllers] = true, + [ActionMap.setSubscribers] = true, + [ActionMap.requestAuthorization] = true, + [ActionMap.getAuthorizationRequests] = true, + [ActionMap.authorize] = true, + [ActionMap.accessControlList] = true, + }) + acl.assignRole(Owner, "admin") + + utils.createActionHandler(ActionMap.info, function(msg) + msg.reply({ + Data = json.encode({ + State = State, + }), + Tags = { + Name = State.name, + Owner = Owner, + Controllers = Controllers, + Subscribers = Subscribers, + }, + }) + end) + + utils.createActionHandler(ActionMap.get, function(msg) + local value = kv_store.getStateValue(msg.Path) + assert(value, "No value foud at path") + + msg.reply({ + Action = ActionMap.get .. "-Notice", + Data = json.encode(value), + }) + end) + + utils.createActionHandler(ActionMap.set, function(msg) + assert(kv_store.authorized(msg.From), "unauthorized") + assert(msg.Path, "No Path provided") + assert(msg.Data, "No Value provided - provide a value in the Data field") + + kv_store.setNestedValue(State, msg.Path, msg.Data) + + msg.reply({ + Action = ActionMap.set .. "-Notice", + Data = "ok", + }) + utils.notifySubscribers({ + Owner = Owner, + Controllers = Controllers, + }, Subscribers) + end) + + utils.createActionHandler(ActionMap.setControllers, function(msg) + assert(kv_store.authorized(msg.From), "unauthorized") + local controllers = json.decode(msg.Data) + assert(type(controllers) == "table", "Controllers must be a table") + + Controllers = controllers + + msg.reply({ + Target = msg.From, + Action = ActionMap.setControllers .. "-Notice", + Controllers = Controllers, + Data = json.encode(Controllers), + }) + utils.notifySubscribers({ + Owner = Owner, + Controllers = Controllers, + }, Subscribers) + end) + + utils.createActionHandler(ActionMap.setSubscribers, function(msg) + assert(kv_store.authorized(msg.From), "unauthorized") + local subscribers = json.decode(msg.Data) + assert(type(subscribers) == "table", "Controllers must be a table") + + Subscribers = subscribers + + msg.reply({ + Target = msg.From, + Action = ActionMap.setSubscribers .. "-Notice", + Subscibers = Subscribers, + Data = json.encode(Subscribers), + }) + utils.notifySubscribers({ + Owner = Owner, + Controllers = Controllers, + }, Subscribers) + end) + + utils.createActionHandler(ActionMap.requestAuthorization, function(msg) + -- Extract role and permissions from the message + local role = msg.Role + local permissions = msg.Permissions + + -- Validate input + assert(type(role) == "string", "Role must be a string") + assert(type(permissions) == "table", "Permissions must be a table") + + -- Store or override the user's authorization request + acl.storeAuthorizationRequest(msg.From, role, permissions) + + msg.reply({ + Action = ActionMap.requestAuthorization .. "-Notice", + Data = json.encode({ + status = "Request stored", + role = role, + permissions = permissions, + }), + }) + end) + + utils.createActionHandler(ActionMap.getAuthorizationRequests, function(msg) + msg.reply({ + Action = ActionMap.getAuthorizationRequests .. "-Notice", + Data = json.encode(acl.authorizationRequests), + }) + end) + + utils.createActionHandler(ActionMap.authorize, function(msg) + assert(kv_store.authorized(msg.From), "unauthorized") + + local user = msg.Address + local role = msg.Role + local permissions = msg.Permissions + + assert(type(user) == "string", "User must be a string") + assert(type(role) == "string", "Role must be a string") + assert(type(permissions) == "table", "Permissions must be a table") + + if not acl.roles[role] then + acl.defineRole(role, permissions) + end + + acl.assignRole(user, role) + + msg.reply({ + Action = ActionMap.authorize .. "-Notice", + Data = json.encode({ + role = role, + user = user, + permissions = permissions, + }), + }) + end) + + utils.createActionHandler(ActionMap.accessControlList, function(msg) + msg.reply({ + Action = ActionMap.accessControlList .. "-Notice", + Data = json.encode({ + users = acl.users, + roles = acl.roles, + authorizationRequests = acl.authorizationRequests, + }), + }) + end) +end + +return kv_store diff --git a/processes/vaultString.lua b/processes/kv_store_string.lua similarity index 99% rename from processes/vaultString.lua rename to processes/kv_store_string.lua index 656bd2a..38f8b73 100644 --- a/processes/vaultString.lua +++ b/processes/kv_store_string.lua @@ -1,4 +1,4 @@ -vaultString = [=[ +local vaultString = [=[ @@ -193,4 +193,4 @@ return SecretVault ]=] -return vaultString \ No newline at end of file +return vaultString diff --git a/processes/registry.lua b/processes/registry.lua deleted file mode 100644 index 477431e..0000000 --- a/processes/registry.lua +++ /dev/null @@ -1,123 +0,0 @@ -local json = require('json') -SecretVault = require('.vaultString') - -local actions = { - spawnVault = "Secretorium.Spawn-Vault", - getVault = "Secretorium.Get-Vault", - getMyVaults = "Secretorium.Get-My-Vaults", - addController = "Add-Controller-Notification" -} - -UserVaultList = UserVaultList or {} -VaultList = VaultList or {} - -local defaultVaultName = "" - -Handlers.add( - "spawn", - Handlers.utils.hasMatchingTag("Action", actions.spawnVault), - function(msg) - - local newVault = Spawn(ao.env.Module.Id, { - Tags = { - ['Secretorium-Registry'] = ao.id, - ['Vault-Name'] = msg['Vault-Name'] or defaultVaultName, - ['Creator'] = msg.From, - ['Id'] = msg['Id'], - ['Authority'] = ao.authorities[1] - } - }).receive({ ['Action'] = 'Spawned' }) - - print("Process = " .. newVault.Process) - print("printing again") - -- print(newVault) - local loadResult = Send({ - Target = newVault.Process, - Action = "Eval", - Data = "Owner = '" .. msg.From .. "' " .. SecretVault - -- Data = SecretVault - }) - - UserVaultList[msg.From] = UserVaultList[msg.From] or {} - UserVaultList[msg.From]["Owner"] = UserVaultList[msg.From]["Owner"] or {} - table.insert(UserVaultList[msg.From]["Owner"], newVault.Process) - - VaultList[newVault.Process] = { - Creator = msg.From, - Controllers = {}, - CreatedAt = msg['Block-Height'], - ['Vault-Name'] = msg['Vault-Name'] or defaultVaultName - } - - Send({ - Target = msg.From, - Action = actions.spawnVault .. "-Notification", - Data = newVault.Process - }) - end -) - - -Handlers.add( - "Add-Controller", - Handlers.utils.hasMatchingTag("Action", actions.addController), - function(msg) - assert(VaultList[msg.From], "Unauthorized") - assert(msg['New-Controller'], "Invalid input, must include new controller") - - UserVaultList[msg['New-Controller']] = UserVaultList[msg['New-Controller']] or {} - UserVaultList[msg['New-Controller']]['Controller'] = UserVaultList[msg['New-Controller']]['Controller'] or {} - table.insert(UserVaultList[msg['New-Controller']]["Controller"], msg.From ) - - table.insert(VaultList[msg.From]['Controllers'], msg['New-Controller']) - end -) - -Handlers.add( - "Get-Vault", - Handlers.utils.hasMatchingTag('Action', actions.getVault), - function(msg) - -- Ensure vault ID is provided and valid - assert(msg['Vault-Id'], "Invalid Input") - assert(VaultList[msg['Vault-Id']], "Vault not found") - - -- Authorization check - local vault = VaultList[msg['Vault-Id']] - local authorized = (vault.Creator == msg.From) - - -- Check if msg.From is in the list of controllers - if not authorized then - for _, controller in ipairs(vault.Controllers or {}) do - if controller == msg.From then - authorized = true - break - end - end - end - - -- Throw unauthorized error if not authorized - assert(authorized, "Unauthorized") - - Send({ - Target = msg.From, - Action = actions.getVault .. "-Notification", - Data = json.encode(vault) - }) - end -) - - -Handlers.add( - 'Get-My-Vaults', - Handlers.utils.hasMatchingTag('Action', actions.getMyVaults), - function(msg) - assert(UserVaultList[msg.From], "No vaults found") - - Send({ - Target = msg.From, - Action = actions.getMyVaults .. "-Notification", - Data = json.encode(UserVaultList[msg.From]) - }) - - end -) \ No newline at end of file diff --git a/tools/build-aos-flavor.js b/processes/tools/build-aos-flavor.js similarity index 100% rename from tools/build-aos-flavor.js rename to processes/tools/build-aos-flavor.js diff --git a/tools/bundle-aos.js b/processes/tools/bundle-aos.js similarity index 100% rename from tools/bundle-aos.js rename to processes/tools/bundle-aos.js diff --git a/tools/constants.js b/processes/tools/constants.js similarity index 100% rename from tools/constants.js rename to processes/tools/constants.js diff --git a/tools/fixtures/aos-C61NgrJDuhwGUsFca1rgfE7ehAKSdgOqPj6aYYy5u4s.wasm b/processes/tools/fixtures/aos-C61NgrJDuhwGUsFca1rgfE7ehAKSdgOqPj6aYYy5u4s.wasm similarity index 100% rename from tools/fixtures/aos-C61NgrJDuhwGUsFca1rgfE7ehAKSdgOqPj6aYYy5u4s.wasm rename to processes/tools/fixtures/aos-C61NgrJDuhwGUsFca1rgfE7ehAKSdgOqPj6aYYy5u4s.wasm diff --git a/tools/fixtures/aos-cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk.wasm b/processes/tools/fixtures/aos-cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk.wasm similarity index 100% rename from tools/fixtures/aos-cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk.wasm rename to processes/tools/fixtures/aos-cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk.wasm diff --git a/tools/lua-bundler.js b/processes/tools/lua-bundler.js similarity index 100% rename from tools/lua-bundler.js rename to processes/tools/lua-bundler.js diff --git a/tools/publish-aos.js b/processes/tools/publish-aos.js similarity index 100% rename from tools/publish-aos.js rename to processes/tools/publish-aos.js diff --git a/tools/scripts/install-deps.js b/processes/tools/scripts/install-deps.js similarity index 100% rename from tools/scripts/install-deps.js rename to processes/tools/scripts/install-deps.js diff --git a/tools/spawn-aos.js b/processes/tools/spawn-aos.js similarity index 100% rename from tools/spawn-aos.js rename to processes/tools/spawn-aos.js diff --git a/tools/utils.js b/processes/tools/utils.js similarity index 100% rename from tools/utils.js rename to processes/tools/utils.js diff --git a/processes/utils.lua b/processes/utils.lua new file mode 100644 index 0000000..7c90363 --- /dev/null +++ b/processes/utils.lua @@ -0,0 +1,381 @@ +-- the majority of this file came from https://github.com/permaweb/aos/blob/main/process/utils.lua + +local json = require(".common.json") +local utils = { _version = "0.0.1" } +local crypto = require(".common.crypto.init") +local Stream = crypto.utils.stream + +local function isArray(table) + if type(table) == "table" then + local maxIndex = 0 + for k, v in pairs(table) do + if type(k) ~= "number" or k < 1 or math.floor(k) ~= k then + return false -- If there's a non-integer key, it's not an array + end + maxIndex = math.max(maxIndex, k) + end + -- If the highest numeric index is equal to the number of elements, it's an array + return maxIndex == #table + end + return false +end + +-- @param {function} fn +-- @param {number} arity +utils.curry = function(fn, arity) + assert(type(fn) == "function", "function is required as first argument") + arity = arity or debug.getinfo(fn, "u").nparams + if arity < 2 then + return fn + end + + return function(...) + local args = { ... } + + if #args >= arity then + return fn(table.unpack(args)) + else + return utils.curry(function(...) + return fn(table.unpack(args), ...) + end, arity - #args) + end + end +end + +--- Concat two Array Tables. +-- @param {table} a +-- @param {table} b +utils.concat = utils.curry(function(a, b) + assert(type(a) == "table", "first argument should be a table that is an array") + assert(type(b) == "table", "second argument should be a table that is an array") + assert(isArray(a), "first argument should be a table") + assert(isArray(b), "second argument should be a table") + + local result = {} + for i = 1, #a do + result[#result + 1] = a[i] + end + for i = 1, #b do + result[#result + 1] = b[i] + end + return result +end, 2) + +--- reduce applies a function to a table +-- @param {function} fn +-- @param {any} initial +-- @param {table} t +utils.reduce = utils.curry(function(fn, initial, t) + assert(type(fn) == "function", "first argument should be a function that accepts (result, value, key)") + assert(type(t) == "table" and isArray(t), "third argument should be a table that is an array") + local result = initial + for k, v in pairs(t) do + if result == nil then + result = v + else + result = fn(result, v, k) + end + end + return result +end, 3) + +-- @param {function} fn +-- @param {table} data +utils.map = utils.curry(function(fn, data) + assert(type(fn) == "function", "first argument should be a unary function") + assert(type(data) == "table" and isArray(data), "second argument should be an Array") + + local function map(result, v, k) + result[k] = fn(v, k) + return result + end + + return utils.reduce(map, {}, data) +end, 2) + +-- @param {function} fn +-- @param {table} data +utils.filter = utils.curry(function(fn, data) + assert(type(fn) == "function", "first argument should be a unary function") + assert(type(data) == "table" and isArray(data), "second argument should be an Array") + + local function filter(result, v, _k) + if fn(v) then + table.insert(result, v) + end + return result + end + + return utils.reduce(filter, {}, data) +end, 2) + +-- @param {function} fn +-- @param {table} t +utils.find = utils.curry(function(fn, t) + assert(type(fn) == "function", "first argument should be a unary function") + assert(type(t) == "table", "second argument should be a table that is an array") + for _, v in pairs(t) do + if fn(v) then + return v + end + end +end, 2) + +-- @param {string} propName +-- @param {string} value +-- @param {table} object +utils.propEq = utils.curry(function(propName, value, object) + assert(type(propName) == "string", "first argument should be a string") + -- assert(type(value) == "string", "second argument should be a string") + assert(type(object) == "table", "third argument should be a table") + + return object[propName] == value +end, 3) + +-- @param {table} data +utils.reverse = function(data) + assert(type(data) == "table", "argument needs to be a table that is an array") + return utils.reduce(function(result, v, i) + result[#data - i + 1] = v + return result + end, {}, data) +end + +-- @param {function} ... +utils.compose = utils.curry(function(...) + local mutations = utils.reverse({ ... }) + + return function(v) + local result = v + for _, fn in pairs(mutations) do + assert(type(fn) == "function", "each argument needs to be a function") + result = fn(result) + end + return result + end +end, 2) + +-- @param {string} propName +-- @param {table} object +utils.prop = utils.curry(function(propName, object) + return object[propName] +end, 2) + +-- @param {any} val +-- @param {table} t +utils.includes = utils.curry(function(val, t) + assert(type(t) == "table", "argument needs to be a table") + return utils.find(function(v) + return v == val + end, t) ~= nil +end, 2) + +-- @param {table} t +utils.keys = function(t) + assert(type(t) == "table", "argument needs to be a table") + local keys = {} + for key in pairs(t) do + table.insert(keys, key) + end + return keys +end + +-- @param {table} t +utils.values = function(t) + assert(type(t) == "table", "argument needs to be a table") + local values = {} + for _, value in pairs(t) do + table.insert(values, value) + end + return values +end + +function utils.camelCase(str) + -- Remove any leading or trailing spaces + str = string.gsub(str, "^%s*(.-)%s*$", "%1") + + -- Convert PascalCase to camelCase + str = string.gsub(str, "^%u", string.lower) + + -- Handle kebab-case, snake_case, and space-separated words + str = string.gsub(str, "[-_%s](%w)", function(s) + return string.upper(s) + end) + + return str +end + +function utils.deepCopy(obj, seen) + if type(obj) ~= "table" then + return obj + end + if seen and seen[obj] then + return seen[obj] + end + local s = seen or {} + local res = setmetatable({}, getmetatable(obj)) + s[obj] = res + for k, v in pairs(obj) do + res[utils.deepCopy(k, s)] = utils.deepCopy(v, s) + end + return res +end + +utils.notices = {} + +-- @param oldMsg table +-- @param newMsg table +-- Add forwarded tags to the new message +-- @return newMsg table +function utils.notices.addForwardedTags(oldMsg, newMsg) + for tagName, tagValue in pairs(oldMsg) do + -- Tags beginning with "X-" are forwarded + if string.sub(tagName, 1, 2) == "X-" then + newMsg[tagName] = tagValue + end + end + return newMsg +end + +function utils.getHandlerNames(handlers) + local names = {} + for _, handler in ipairs(handlers.list) do + table.insert(names, handler.name) + end + return names +end + +function utils.errorHandler(err) + return debug.traceback(err) +end + +function utils.createHandler(tagName, tagValue, handler, position) + assert( + type(position) == "string" or type(position) == "nil", + utils.errorHandler("Position must be a string or nil") + ) + assert( + position == nil or position == "add" or position == "prepend" or position == "append", + "Position must be one of 'add', 'prepend', 'append'" + ) + return Handlers[position or "add"]( + utils.camelCase(tagValue), + Handlers.utils.continue(Handlers.utils.hasMatchingTag(tagName, tagValue)), + function(msg) + print("Handling Action [" .. msg.Id .. "]: " .. tagValue) + local handlerStatus, handlerRes = xpcall(function() + handler(msg) + end, utils.errorHandler) + + if not handlerStatus then + ao.send({ + Target = msg.From, + Action = "Invalid-" .. tagValue .. "-Notice", + Error = tagValue .. "-Error", + ["Message-Id"] = msg.Id, + Data = handlerRes, + }) + end + + return handlerRes + end + ) +end + +function utils.createActionHandler(action, msgHandler, position) + return utils.createHandler("Action", action, msgHandler, position) +end + +function utils.createForwardedActionHandler(action, msgHandler, position) + return utils.createHandler("X-Action", action, msgHandler, position) +end + +function utils.controllerTableFromArray(t) + assert(type(t) == "table", "argument needs to be a table") + local map = {} + for _, v in ipairs(t) do + map[v] = true + end + return map +end + +--- Updates the affiliations for a store. +---@param storeId string ID of the store. +---@param newStore table new store object. +---@param addresses table addresses table. +---@param stores table stores table. +function utils.updateAffiliations(storeId, newStore, addresses, stores) + -- Remove previous affiliations for old owner and controllers + local maybeOldStore = stores[storeId] + local newAffliates = utils.affiliatesForStore(newStore) + + -- Remove stale address affiliations + if maybeOldStore ~= nil then + local oldAffliates = utils.affiliatesForStore(maybeOldStore) + for oldAffliate, _ in pairs(oldAffliates) do + if not newAffliates[oldAffliate] and addresses[oldAffliate] then + addresses[oldAffliate][storeId] = nil + end + end + end + + -- Create new affiliations + for address, _ in pairs(newAffliates) do + -- Instantiate the address table if it doesn't exist + addresses[address] = addresses[address] or {} + -- Finalize the affiliation + addresses[address][storeId] = true + end + + -- Update the stores table with the newest store state + if #utils.keys(newAffliates) == 0 then + stores[storeId] = nil + else + stores[storeId] = newStore + end +end + +function utils.affiliatesForStore(store) + local affliates = {} + if store.Owner then + affliates[store.Owner] = true + end + for address, _ in pairs(store.Controllers) do + affliates[address] = true + end + return affliates +end + +function utils.affiliationsForAddress(address, ants) + local affiliations = { + Owned = {}, + Controlled = {}, + } + for antId, ant in pairs(ants) do + if ant.Owner == address then + table.insert(affiliations.Owned, antId) + elseif ant.Controllers[address] then + table.insert(affiliations.Controlled, antId) + end + end + return affiliations +end + +-- returns hex string hash of the property value +function utils.hashGlobalProperty(property) + local stream = Stream.fromString(json.encode(_G[property])) + local hash = crypto.digest.sha2_256(stream) + return tostring(hash.asHex()) +end + +function utils.notifySubscribers(data, addresses) + for _, address in ipairs(addresses) do + ao.send({ + Target = address, + Action = "Subscriber-Notice", + Data = json.encode(data), + }) + end +end + +return utils diff --git a/processes/vault.lua b/processes/vault.lua deleted file mode 100644 index cbe4654..0000000 --- a/processes/vault.lua +++ /dev/null @@ -1,186 +0,0 @@ -local json = require('json') - -SecretVault = SecretVault or {} -SecretVault.State = SecretVault.State or {} -SecretVault.PubSub = SecretVault.PubSub or { Owner, ao.env.Process.Tags['Secretorium-Registry'] } -SecretVault.Controllers = SecretVault.Controllers or { Owner } - -SecretVault.actions = { - set = 'Set', - get = 'Get', - addController = "Add-Controller", - addPubSub = "Add-PubSub" -} - -function SecretVault.authorized(From) - for _, value in ipairs(SecretVault.Controllers) do - if value == From then - return true - end - end - return false -end - --- Set value in nested table based on dot-separated path -function SecretVault.setNestedValue(table, path, value) - local current = table - for segment in string.gmatch(path, "[^%.]+") do - if not current[segment] then - current[segment] = {} - end - current = current[segment] - end - current = value -end - --- Notify all PubSub users of a change -function SecretVault.notifyPubSub(action, path, newController) - local data - if path then - data = "State change at path: " .. path - else - if newController then - data = "New Controller added: " .. newController - end - end - for _, subscriber in ipairs(SecretVault.PubSub) do - local message = { - Target = subscriber, - Action = action .. "-Notification", - Path = path, - ['New-Controller'] = newController, - Data = data - } - Send(message) - end -end - --- Notify owner of unauthorized access attempts -function SecretVault.notifyUnauthorized(action, from) - local message = { - Target = Owner, - Action = "Unauthorized Attempt-Notification", - Data = "Unauthorized attempt to perform action: " .. action .. " by: " .. from - } - Send(message) -end - -Handlers.add( - "Info", - Handlers.utils.hasMatchingTag("Action", "Info"), - function(msg) - Send({ - Target = msg.From, - Action = "Info", - Data = "Owner is: " .. Owner - }) - end -) - -Handlers.add( - "Set", - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.set), - function(msg) - local isAuthorized = SecretVault.authorized(msg.From) - if not isAuthorized then - SecretVault.notifyUnauthorized(SecretVault.actions.set, msg.From) - end - assert(isAuthorized, "unauthorized") - assert(msg.Path, "No Path provided") - assert(msg.Value, "No Value provided") - - SecretVault.setNestedValue(SecretVault.State, msg.Path, msg.Value) - - local message = { - Target = msg.From, - Action = SecretVault.actions.set .. "-Notification", - Path = msg.Path, - Value = msg.Value, - Data = "Value set successfully" - } - Send(message) - SecretVault.notifyPubSub(SecretVault.actions.set, msg.Path) - end -) - -Handlers.add( - "Get", - Handlers.utils.hasMatchingTag('Action', SecretVault.actions.get), - function(msg) - local current = SecretVault.State - if msg.Path then - for segment in string.gmatch(msg.Path, "[^%.]+") do - current = current[segment] - assert(current, "Path not found") - end - end - - local data = json.encode(current) - - local message = { - Target = msg.From, - Action = SecretVault.actions.get .. "-Notification", - Path = msg.Path, - Value = data, - Data = data - } - Send(message) - end -) - -Handlers.add( - "Add-Controller", - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addController), - function(msg) - if msg.From ~= Owner then - SecretVault.notifyUnauthorized(SecretVault.actions.addController, msg.From) - end - assert(msg.From == Owner, "unauthorized") - assert(msg["New-Controller"], "New User not provided") - assert(SecretVault.authorized(msg['New-Controller']) == false, "Already authorized") - table.insert(SecretVault.Controllers, msg["New-Controller"]) - - local message = { - Target = msg.From, - Action = SecretVault.actions.addController .. "-Notification", - ["New-Controller"] = msg["New-Controller"], - Data = "User " .. msg["New-Controller"] .. " added successfully as a controller" - } - Send(message) - Send({ - Target = msg["New-Controller"], - Action = SecretVault.actions.addController .. "-Notification", - Data = "You have been added as an authorized controller for SecretVault" - }) - SecretVault.notifyPubSub(SecretVault.actions.addController, nil, msg["New-Controller"]) - end -) - -Handlers.add( - "Add-PubSub", - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addPubSub), - function(msg) - if msg.From ~= Owner then - SecretVault.notifyUnauthorized(SecretVault.actions.addPubSub, msg.From) - end - assert(msg.From == Owner, "unauthorized") - assert(msg["New-Controller"], "New User not provided") - table.insert(SecretVault.PubSub, msg["New-Controller"]) - - local message = { - Target = msg.From, - Action = SecretVault.actions.addPubSub .. "-Notification", - ["New-Controller"] = msg["New-Controller"], - Data = "User " .. msg["New-Controller"] .. " added successfully to PubSub" - } - Send(message) - Send({ - Target = msg["New-Controller"], - Action = SecretVault.actions.addPubSub .. "-Notification", - Data = "You have been added as a PubSub user for SecretVault" - }) - end -) - - -return SecretVault diff --git a/src/utils/vault.ts b/src/utils/vault.ts index 7053c83..24e72df 100644 --- a/src/utils/vault.ts +++ b/src/utils/vault.ts @@ -1,6 +1,6 @@ import { SIG_CONFIG, SignatureConfig } from '@dha-team/arbundles'; import { CollaboratorConfig } from '@src/types/vault'; -import { split } from 'shamir-secret-sharing'; +import { split, combine } from 'shamir-secret-sharing'; import { encryptWithArweavePublicKey } from './arweave'; import { toB64Url } from './encoding'; From 414ec5e9f16dfb0af5d99048d79f4435586516b2 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Sun, 3 Nov 2024 13:50:22 -0600 Subject: [PATCH 9/9] feat(processes): add kv registry and process for secretorium --- package.json | 7 +- processes/kv_registry.lua | 38 +- processes/kv_registry_init.lua | 3 + processes/kv_store.lua | 17 +- processes/kv_store_init.lua | 3 + processes/kv_store_string.lua | 409 ++++++++++-------- processes/tests/registry.test.js | 110 +++++ processes/tests/store.test.js | 246 +++++++++++ processes/tools/build-aos-flavor.js | 178 -------- processes/tools/constants.js | 8 +- ...rBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk.wasm | Bin 731229 -> 0 bytes processes/tools/spawn-aos.js | 53 --- processes/tools/utils.js | 23 +- processes/utils.lua | 13 +- 14 files changed, 660 insertions(+), 448 deletions(-) create mode 100644 processes/kv_registry_init.lua create mode 100644 processes/kv_store_init.lua create mode 100644 processes/tests/registry.test.js create mode 100644 processes/tests/store.test.js delete mode 100644 processes/tools/build-aos-flavor.js delete mode 100644 processes/tools/fixtures/aos-cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk.wasm delete mode 100644 processes/tools/spawn-aos.js diff --git a/package.json b/package.json index 614cc44..7597637 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,12 @@ "format": "prettier --write .", "preview": "vite preview", "prepare": "husky install", - "postinstall": "husky install" + "postinstall": "husky install", + "kv-registry:build": "node processes/tools/bundle-aos.js --path=\"./processes/kv_registry_init.lua\" --output=\"./processes/dist/kv_registry/aos-bundled.lua\"", + "kv-store:build": "node processes/tools/bundle-aos.js --path=\"./processes/kv_store_init.lua\" --output=\"./processes/dist/kv_store/aos-bundled.lua\"", + "aos:build": "yarn kv-registry:build && yarn kv-store:build", + "aos:publish": "node processes/tools/bundle-aos.js && node tools/publish-aos.js", + "aos:test": "node --test --test-concurrency 1 --experimental-wasm-memory64 processes/tests/*.test.js" }, "dependencies": { "@dha-team/arbundles": "^1.0.1", diff --git a/processes/kv_registry.lua b/processes/kv_registry.lua index 4987aab..14de3f4 100644 --- a/processes/kv_registry.lua +++ b/processes/kv_registry.lua @@ -33,35 +33,51 @@ function kvRegistry.init() local KVStoreList = kvRegistry.KVStoreList local defaultKvStoreName = "Key_Value_Store" - utils.createActionHandler(ActionMap.spawnKVProcess, function(msg) + utils.createActionHandler(ActionMap.spawnKVStore, function(msg) -- initialize UserList[msg.From] if it doesn't exist UserList[msg.From] = UserList[msg.From] or { Owned = {}, Controlled = {}, } - local kvStoreSpawnMsg = Spawn(ao.env.Module.Id, { + Spawn(ao.env.Module.Id, { Tags = { ["KV-Registry"] = ao.id, ["KV-Store-Name"] = msg["KV-Store-Name"] or defaultKvStoreName, - Creator = msg.From, + ["X-Creator"] = msg.From, Authority = ao.authorities[1], ["X-Original-Msg-Id"] = msg.Id, ["On-Boot"] = "Data", }, Data = KVStoreCode, - }).receive({ Action = "Spawned", ["X-Original-Msg-Id"] = msg.Id }) + }) + end) - UserList[msg.From].Owned[kvStoreSpawnMsg.Process] = true + utils.createActionHandler("Spawned", function(msg) + local storeId = msg.Process + local creator = msg["X-Creator"] + local originalMsgId = msg["X-Original-Msg-Id"] + assert(storeId, "msg.Process is required") + assert(creator, "msg['X-Creator'] is required") + assert(originalMsgId, "msg['X-Original-Msg-Id'] is required") + + -- initialize UserList[msg.From] if it doesn't exist + UserList[creator] = UserList[creator] or { + Owned = {}, + Controlled = {}, + } + UserList[creator].Owned[storeId] = true - KVStoreList[kvStoreSpawnMsg.Process] = { - Owner = msg.From, + KVStoreList[storeId] = { + Owner = creator, Controllers = {}, } - msg.reply({ - Action = ActionMap.spawnKVProcess .. "-Notice", - ["KV-Store-Id"] = kvStoreSpawnMsg.Process, - Data = kvStoreSpawnMsg.Process, + ao.send({ + Target = creator, + Action = ActionMap.spawnKVStore .. "-Notice", + ["Message-Id"] = originalMsgId, + ["KV-Store-Id"] = storeId, + Data = msg.Process, }) end) diff --git a/processes/kv_registry_init.lua b/processes/kv_registry_init.lua new file mode 100644 index 0000000..0ebd879 --- /dev/null +++ b/processes/kv_registry_init.lua @@ -0,0 +1,3 @@ +local kv_registry = require(".kv_registry") + +kv_registry.init() diff --git a/processes/kv_store.lua b/processes/kv_store.lua index 1a5282d..71a6161 100644 --- a/processes/kv_store.lua +++ b/processes/kv_store.lua @@ -108,7 +108,7 @@ function kv_store.init() utils.createActionHandler(ActionMap.get, function(msg) local value = kv_store.getStateValue(msg.Path) - assert(value, "No value foud at path") + assert(value, "No value found at path") msg.reply({ Action = ActionMap.get .. "-Notice", @@ -117,11 +117,16 @@ function kv_store.init() end) utils.createActionHandler(ActionMap.set, function(msg) - assert(kv_store.authorized(msg.From), "unauthorized") assert(msg.Path, "No Path provided") assert(msg.Data, "No Value provided - provide a value in the Data field") + assert( + acl.hasPermission(msg.From, ActionMap.set, msg.Path), + string.format("User %s is not authorized to set %s", msg.From, msg.Path) + ) + local decodeStatus, decodeRes = pcall(json.decode, msg.Data) + local value = decodeStatus and decodeRes or msg.Data - kv_store.setNestedValue(State, msg.Path, msg.Data) + kv_store.setNestedValue(State, msg.Path, value) msg.reply({ Action = ActionMap.set .. "-Notice", @@ -174,7 +179,7 @@ function kv_store.init() utils.createActionHandler(ActionMap.requestAuthorization, function(msg) -- Extract role and permissions from the message local role = msg.Role - local permissions = msg.Permissions + local permissions = json.decode(msg.Permissions) -- Validate input assert(type(role) == "string", "Role must be a string") @@ -205,7 +210,7 @@ function kv_store.init() local user = msg.Address local role = msg.Role - local permissions = msg.Permissions + local permissions = json.decode(msg.Permissions) assert(type(user) == "string", "User must be a string") assert(type(role) == "string", "Role must be a string") @@ -216,6 +221,8 @@ function kv_store.init() end acl.assignRole(user, role) + -- Remove the user's authorization request + acl.clearAuthorizationRequest(user) msg.reply({ Action = ActionMap.authorize .. "-Notice", diff --git a/processes/kv_store_init.lua b/processes/kv_store_init.lua new file mode 100644 index 0000000..7c77e89 --- /dev/null +++ b/processes/kv_store_init.lua @@ -0,0 +1,3 @@ +local kv_store = require(".kv_store") + +kv_store.init() diff --git a/processes/kv_store_string.lua b/processes/kv_store_string.lua index 38f8b73..19b5d15 100644 --- a/processes/kv_store_string.lua +++ b/processes/kv_store_string.lua @@ -1,195 +1,250 @@ local vaultString = [=[ +local json = require("json") +local utils = require(".utils") +local acl = require(".acl") + +local kv_store = {} + +kv_store.ActionMap = { + info = "Info", + set = "KV-Store.Set", + get = "KV-Store.Get", + setControllers = "KV-Store.Set-Controllers", + setSubscribers = "KV-Store.Set-Subscribers", + -- ACL + requestAuthorization = "KV-Store.Request-Authorization", + --[[ example secret configuration path. This would allow the collaborator to set their config. + Once they have the access role they can set their config and secretorium can use that to set the shares. + { + secretName = { + collaborators = { + ["id"] = { + encryptionPublicKey = "publicKey", + chainConfig = {...config} + } + } + } + } + ]] + getAuthorizationRequests = "KV-Store.Get-Authorization-Requests", + authorize = "KV-Store.Authorize", + accessControlList = "KV-Store.Access-Control-List", +} +Owner = ao.env.Process.Tags["Creator"] or Owner -local json = require('json') - -SecretVault = SecretVault or {} -SecretVault.State = SecretVault.State or {} -SecretVault.PubSub = SecretVault.PubSub or { Owner, ao.env.Process.Tags['Secretorium-Registry'] } -SecretVault.Controllers = SecretVault.Controllers or { Owner } - -SecretVault.actions = { - set = 'Set', - get = 'Get', - addController = "Add-Controller", - addPubSub = "Add-PubSub" +State = State or { + name = ao.env.Process.Tags["KV-Store-Name"], } - -function SecretVault.authorized(From) - for _, value in ipairs(SecretVault.Controllers) do - if value == From then - return true - end - end - return false +Subscribers = Subscribers or { ao.env.Process.Tags["KV-Registry"] } +Controllers = Controllers or { Owner } + +function kv_store.authorized(From) + if From == Owner then + return true + end + for _, value in ipairs(Controllers) do + if value == From then + return true + end + end + return false end --- Set value in nested table based on dot-separated path -function SecretVault.setNestedValue(table, path, value) - local current = table - for segment in string.gmatch(path, "[^%.]+") do - if not current[segment] then - current[segment] = {} - end - current = current[segment] - end - current = value +function kv_store.setNestedValue(tbl, path, value) + local current = tbl + for segment in string.gmatch(path, "[^%.]+") do + -- Create the nested table if it doesn't exist + if not current[segment] then + current[segment] = {} + end + -- Move down the path unless we're at the final segment + if segment == string.match(path, "[^%.]+$") then + current[segment] = value + else + current = current[segment] + end + end end --- Notify all PubSub users of a change -function SecretVault.notifyPubSub(action, path, newController) - local data - if path then - data = "State change at path: " .. path - else - if newController then - data = "New Controller added: " .. newController - end - end - for _, subscriber in ipairs(SecretVault.PubSub) do - local message = { - Target = subscriber, - Action = action .. "-Notification", - Path = path, - ['New-Controller'] = newController, - Data = data - } - Send(message) - end +function kv_store.getStateValue(path) + local current = utils.deepCopy(State) + for segment in string.gmatch(path, "[^%.]+") do + -- Check if the segment exists in the current table + if current[segment] == nil then + return nil -- Return nil if any part of the path is missing + end + current = current[segment] + end + return current end --- Notify owner of unauthorized access attempts -function SecretVault.notifyUnauthorized(action, from) - local message = { - Target = Owner, - Action = "Unauthorized Attempt-Notification", - Data = "Unauthorized attempt to perform action: " .. action .. " by: " .. from - } - Send(message) +function kv_store.init() + local ActionMap = kv_store.ActionMap + + acl.defineRole("admin", { + [ActionMap.set] = true, + [ActionMap.setControllers] = true, + [ActionMap.setSubscribers] = true, + [ActionMap.requestAuthorization] = true, + [ActionMap.getAuthorizationRequests] = true, + [ActionMap.authorize] = true, + [ActionMap.accessControlList] = true, + }) + acl.assignRole(Owner, "admin") + + utils.createActionHandler(ActionMap.info, function(msg) + msg.reply({ + Data = json.encode({ + State = State, + }), + Tags = { + Name = State.name, + Owner = Owner, + Controllers = Controllers, + Subscribers = Subscribers, + }, + }) + end) + + utils.createActionHandler(ActionMap.get, function(msg) + local value = kv_store.getStateValue(msg.Path) + assert(value, "No value foud at path") + + msg.reply({ + Action = ActionMap.get .. "-Notice", + Data = json.encode(value), + }) + end) + + utils.createActionHandler(ActionMap.set, function(msg) + assert(kv_store.authorized(msg.From), "unauthorized") + assert(msg.Path, "No Path provided") + assert(msg.Data, "No Value provided - provide a value in the Data field") + + kv_store.setNestedValue(State, msg.Path, msg.Data) + + msg.reply({ + Action = ActionMap.set .. "-Notice", + Data = "ok", + }) + utils.notifySubscribers({ + Owner = Owner, + Controllers = Controllers, + }, Subscribers) + end) + + utils.createActionHandler(ActionMap.setControllers, function(msg) + assert(kv_store.authorized(msg.From), "unauthorized") + local controllers = json.decode(msg.Data) + assert(type(controllers) == "table", "Controllers must be a table") + + Controllers = controllers + + msg.reply({ + Target = msg.From, + Action = ActionMap.setControllers .. "-Notice", + Controllers = Controllers, + Data = json.encode(Controllers), + }) + utils.notifySubscribers({ + Owner = Owner, + Controllers = Controllers, + }, Subscribers) + end) + + utils.createActionHandler(ActionMap.setSubscribers, function(msg) + assert(kv_store.authorized(msg.From), "unauthorized") + local subscribers = json.decode(msg.Data) + assert(type(subscribers) == "table", "Controllers must be a table") + + Subscribers = subscribers + + msg.reply({ + Target = msg.From, + Action = ActionMap.setSubscribers .. "-Notice", + Subscibers = Subscribers, + Data = json.encode(Subscribers), + }) + utils.notifySubscribers({ + Owner = Owner, + Controllers = Controllers, + }, Subscribers) + end) + + utils.createActionHandler(ActionMap.requestAuthorization, function(msg) + -- Extract role and permissions from the message + local role = msg.Role + local permissions = msg.Permissions + + -- Validate input + assert(type(role) == "string", "Role must be a string") + assert(type(permissions) == "table", "Permissions must be a table") + + -- Store or override the user's authorization request + acl.storeAuthorizationRequest(msg.From, role, permissions) + + msg.reply({ + Action = ActionMap.requestAuthorization .. "-Notice", + Data = json.encode({ + status = "Request stored", + role = role, + permissions = permissions, + }), + }) + end) + + utils.createActionHandler(ActionMap.getAuthorizationRequests, function(msg) + msg.reply({ + Action = ActionMap.getAuthorizationRequests .. "-Notice", + Data = json.encode(acl.authorizationRequests), + }) + end) + + utils.createActionHandler(ActionMap.authorize, function(msg) + assert(kv_store.authorized(msg.From), "unauthorized") + + local user = msg.Address + local role = msg.Role + local permissions = msg.Permissions + + assert(type(user) == "string", "User must be a string") + assert(type(role) == "string", "Role must be a string") + assert(type(permissions) == "table", "Permissions must be a table") + + if not acl.roles[role] then + acl.defineRole(role, permissions) + end + + acl.assignRole(user, role) + + msg.reply({ + Action = ActionMap.authorize .. "-Notice", + Data = json.encode({ + role = role, + user = user, + permissions = permissions, + }), + }) + end) + + utils.createActionHandler(ActionMap.accessControlList, function(msg) + msg.reply({ + Action = ActionMap.accessControlList .. "-Notice", + Data = json.encode({ + users = acl.users, + roles = acl.roles, + authorizationRequests = acl.authorizationRequests, + }), + }) + end) end -Handlers.add( - "Info", - Handlers.utils.hasMatchingTag("Action", "Info"), - function(msg) - Send({ - Target = msg.From, - Action = "Info", - Data = "Owner is: " .. Owner - }) - end -) - -Handlers.add( - "Set", - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.set), - function(msg) - local isAuthorized = SecretVault.authorized(msg.From) - if not isAuthorized then - SecretVault.notifyUnauthorized(SecretVault.actions.set, msg.From) - end - assert(isAuthorized, "unauthorized") - assert(msg.Path, "No Path provided") - assert(msg.Value, "No Value provided") - - SecretVault.setNestedValue(SecretVault.State, msg.Path, msg.Value) - - local message = { - Target = msg.From, - Action = SecretVault.actions.set .. "-Notification", - Path = msg.Path, - Value = msg.Value, - Data = "Value set successfully" - } - Send(message) - SecretVault.notifyPubSub(SecretVault.actions.set, msg.Path) - end -) - -Handlers.add( - "Get", - Handlers.utils.hasMatchingTag('Action', SecretVault.actions.get), - function(msg) - local current = SecretVault.State - if msg.Path then - for segment in string.gmatch(msg.Path, "[^%.]+") do - current = current[segment] - assert(current, "Path not found") - end - end - - local data = json.encode(current) - - local message = { - Target = msg.From, - Action = SecretVault.actions.get .. "-Notification", - Path = msg.Path, - Value = data, - Data = data - } - Send(message) - end -) - -Handlers.add( - "Add-Controller", - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addController), - function(msg) - if msg.From ~= Owner then - SecretVault.notifyUnauthorized(SecretVault.actions.addController, msg.From) - end - assert(msg.From == Owner, "unauthorized") - assert(msg["New-Controller"], "New User not provided") - assert(SecretVault.authorized(msg['New-Controller']) == false, "Already authorized") - table.insert(SecretVault.Controllers, msg["New-Controller"]) - - local message = { - Target = msg.From, - Action = SecretVault.actions.addController .. "-Notification", - ["New-Controller"] = msg["New-Controller"], - Data = "User " .. msg["New-Controller"] .. " added successfully as a controller" - } - Send(message) - Send({ - Target = msg["New-Controller"], - Action = SecretVault.actions.addController .. "-Notification", - Data = "You have been added as an authorized controller for SecretVault" - }) - SecretVault.notifyPubSub(SecretVault.actions.addController, nil, msg["New-Controller"]) - end -) - -Handlers.add( - "Add-PubSub", - Handlers.utils.hasMatchingTag("Action", SecretVault.actions.addPubSub), - function(msg) - if msg.From ~= Owner then - SecretVault.notifyUnauthorized(SecretVault.actions.addPubSub, msg.From) - end - assert(msg.From == Owner, "unauthorized") - assert(msg["New-Controller"], "New User not provided") - table.insert(SecretVault.PubSub, msg["New-Controller"]) - - local message = { - Target = msg.From, - Action = SecretVault.actions.addPubSub .. "-Notification", - ["New-Controller"] = msg["New-Controller"], - Data = "User " .. msg["New-Controller"] .. " added successfully to PubSub" - } - Send(message) - Send({ - Target = msg["New-Controller"], - Action = SecretVault.actions.addPubSub .. "-Notification", - Data = "You have been added as a PubSub user for SecretVault" - }) - end -) - - -return SecretVault +return kv_store +kv_store.init() ]=] diff --git a/processes/tests/registry.test.js b/processes/tests/registry.test.js new file mode 100644 index 0000000..00311ba --- /dev/null +++ b/processes/tests/registry.test.js @@ -0,0 +1,110 @@ +import assert from 'node:assert'; +import { before, describe, it } from 'node:test'; + +import { + AO_LOADER_HANDLER_ENV, + DEFAULT_HANDLE_OPTIONS, + STUB_ADDRESS, +} from '../tools/constants.js'; +import { createKVRegistryAosLoader, getHandlers } from '../tools/utils.js'; + +describe('KV_Registry', async () => { + let handle; + let startMemory; + + before(async () => { + const loader = await createKVRegistryAosLoader(); + handle = loader.handle; + startMemory = loader.memory; + }); + + async function sendMessage(options = {}, mem = startMemory) { + return handle( + mem, + { + ...DEFAULT_HANDLE_OPTIONS, + ...options, + }, + AO_LOADER_HANDLER_ENV, + ); + } + + it('should have correct handlers', async () => { + const handlersRes = await getHandlers(sendMessage, startMemory); + const handlers = [ + '_eval', + '_default', + 'kVRegistry.SpawnKVStore', + 'kVRegistry.GetKVStores', + 'kVStore.SubscriberNotice', + ]; + + assert(handlers.every((handler) => handlersRes.includes(handler))); + }); + + it('should spawn a kv store', async () => { + const res = await sendMessage({ + Tags: [{ name: 'Action', value: 'KV-Registry.Spawn-KV-Store' }], + }); + + assert(res.Spawns.length === 1); + }); + + it('should retrieve KV stores for user', async () => { + const spawnRes = await sendMessage({ + Tags: [{ name: 'Action', value: 'KV-Registry.Spawn-KV-Store' }], + }); + + const spawnCbRes = await sendMessage( + { + Tags: [ + { name: 'Action', value: 'Spawned' }, + { name: 'X-Original-Msg-Id', value: STUB_ADDRESS }, + { name: 'Process', value: STUB_ADDRESS }, + { name: 'X-Creator', value: STUB_ADDRESS }, + ], + }, + spawnRes.Memory, + ); + + const kvStoresRes = await sendMessage( + { + Tags: [ + { name: 'Action', value: 'KV-Registry.Get-KV-Stores' }, + { name: 'Address', value: STUB_ADDRESS }, + ], + }, + spawnCbRes.Memory, + ); + + const kvStores = JSON.parse(kvStoresRes.Messages[0].Data); + assert(kvStores.Owned.length === 1); + + // adding notice test + const noticeRes = await sendMessage( + { + From: STUB_ADDRESS, + Tags: [{ name: 'Action', value: 'KV-Store.Subscriber-Notice' }], + Data: JSON.stringify({ + Owner: STUB_ADDRESS, + Controllers: { [''.padEnd(43, '2')]: true }, + }), + }, + kvStoresRes.Memory, + ); + + const controllerKvStoresRes = await sendMessage( + { + Tags: [ + { name: 'Action', value: 'KV-Registry.Get-KV-Stores' }, + { name: 'Address', value: ''.padEnd(43, '2') }, + ], + }, + noticeRes.Memory, + ); + assert( + JSON.parse(controllerKvStoresRes.Messages[0].Data).Controlled.length === + 1, + ); + }); +}); diff --git a/processes/tests/store.test.js b/processes/tests/store.test.js new file mode 100644 index 0000000..48c9caf --- /dev/null +++ b/processes/tests/store.test.js @@ -0,0 +1,246 @@ +import assert from 'node:assert'; +import { before, describe, it } from 'node:test'; + +import { + AO_LOADER_HANDLER_ENV, + DEFAULT_HANDLE_OPTIONS, + STUB_ADDRESS, +} from '../tools/constants.js'; +import { createKVStoreAosLoader, getHandlers } from '../tools/utils.js'; + +describe('KVStore', async () => { + let handle; + let startMemory; + + before(async () => { + const loader = await createKVStoreAosLoader(); + handle = loader.handle; + startMemory = loader.memory; + }); + + async function sendMessage(options = {}, mem = startMemory) { + return handle( + mem, + { + ...DEFAULT_HANDLE_OPTIONS, + ...options, + }, + AO_LOADER_HANDLER_ENV, + ); + } + + async function setData({ path, data, options, memory }) { + return sendMessage( + { + Tags: [ + { name: 'Action', value: 'KV-Store.Set' }, + { name: 'Path', value: path }, + ], + Data: data, + ...(options ?? {}), + }, + memory, + ); + } + + async function getData({ path, options, memory }) { + return sendMessage( + { + Tags: [ + { name: 'Action', value: 'KV-Store.Get' }, + { name: 'Path', value: path }, + ], + ...(options ?? {}), + }, + memory, + ); + } + + async function getAuthorizedUsers(mem) { + const res = await sendMessage( + { + Tags: [{ name: 'Action', value: 'KV-Store.Access-Control-List' }], + }, + mem, + ); + return JSON.parse(res.Messages[0].Data); + } + + async function getAuthorizationRequests(mem) { + const res = await sendMessage( + { + Tags: [ + { name: 'Action', value: 'KV-Store.Get-Authorization-Requests' }, + ], + }, + mem, + ); + return JSON.parse(res.Messages[0].Data); + } + + async function setControllers({ controllers, memory }) { + return sendMessage( + { + Tags: [ + { name: 'Action', value: 'KV-Store.Set-Controllers' }, + { name: 'Controllers', value: JSON.stringify(controllers) }, + ], + }, + memory, + ); + } + + async function getControllers(mem) { + const res = await sendMessage( + { + Tags: [{ name: 'Action', value: 'Info' }], + }, + mem, + ); + return res.Messages[0].tags.find((tag) => tag.name === 'Controllers').value; + } + + async function setSubscribers({ subscribers, memory }) { + return sendMessage( + { + Tags: [ + { name: 'Action', value: 'KV-Store.Set-Subscribers' }, + { name: 'Subscribers', value: JSON.stringify(subscribers) }, + ], + }, + memory, + ); + } + async function getSubscribers(mem) { + const res = await sendMessage( + { + Tags: [{ name: 'Action', value: 'Info' }], + }, + mem, + ); + return res.Messages[0].tags.find((tag) => tag.name === 'Subscribers').value; + } + + it('should get store info', async () => { + const info = await sendMessage({ + Tags: [{ name: 'Action', value: 'Info' }], + }); + assert(info.Messages[0]); + }); + + // doubles as a test for _eval + it('should have the correct handlers', async () => { + const handlersRes = await getHandlers(sendMessage, startMemory); + const handlers = [ + '_eval', + '_default', + 'info', + 'kVStore.Get', + 'kVStore.Set', + 'kVStore.SetControllers', + 'kVStore.SetSubscribers', + 'kVStore.RequestAuthorization', + 'kVStore.GetAuthorizationRequests', + 'kVStore.Authorize', + 'kVStore.AccessControlList', + ]; + assert(handlers.every((handler) => handlersRes.includes(handler))); + }); + + it('should set a value as owner', async () => { + const res = await setData({ path: 'foo', data: 'bar' }); + const getValueRes = await getData({ path: 'foo', memory: res.Memory }); + assert(JSON.parse(getValueRes.Messages[0].Data) === 'bar'); + }); + + it('should set a value as authorized user', async () => { + const role = 'collaborator-' + ''.padEnd(43, '2'); + const func = 'KV-Store.Set'; + const path = `^(.-)%.collaborators%.(${''.padEnd(43, '2')})$`; + const requestAuthRes = await sendMessage({ + From: ''.padEnd(43, '2'), + Owner: ''.padEnd(43, '2'), + Tags: [ + { name: 'Action', value: 'KV-Store.Request-Authorization' }, + { name: 'Role', value: role }, + { + name: 'Permissions', + value: JSON.stringify({ + [func]: [path], + }), + }, + ], + }); + const authRequests = await getAuthorizationRequests(requestAuthRes.Memory); + assert(authRequests[''.padEnd(43, '2')].role === role); + assert(authRequests[''.padEnd(43, '2')].permissions[func].includes(path)); + + const authRes = await sendMessage( + { + Tags: [ + { name: 'Action', value: 'KV-Store.Authorize' }, + { name: 'Role', value: role }, + { name: 'Permissions', value: JSON.stringify({ [func]: [path] }) }, + { name: 'Address', value: ''.padEnd(43, '2') }, + ], + }, + requestAuthRes.Memory, + ); + + const authorizedUsers = await getAuthorizedUsers(authRes.Memory); + const newUser = authorizedUsers.users[''.padEnd(43, '2')]; + assert( + authorizedUsers.authorizationRequests.length === 0, + 'Auth request was not removed', + ); + assert(newUser, 'failed to authorize user'); + assert(newUser[role], 'Role was not set properly for authorized user'); + assert( + authorizedUsers.roles[role][func].includes(path), + 'Path in role was not set properly for authorized user', + ); + + const config = { + encryptionPublicKey: 'foo', + }; + + const setDataRes = await setData({ + path: `github.collaborators.${''.padEnd(43, '2')}`, + data: JSON.stringify(config), + memory: authRes.Memory, + options: { From: ''.padEnd(43, '2'), Owner: ''.padEnd(43, '2') }, + }); + //console.dir(setDataRes, { depth: null }); + const getValueRes = await getData({ + path: `github.collaborators.${''.padEnd(43, '2')}`, + memory: setDataRes.Memory, + }); + const configRes = JSON.parse(getValueRes.Messages[0].Data); + assert( + config.encryptionPublicKey === configRes.encryptionPublicKey, + 'Data was not set properly by authorized user', + ); + + await it('should not set a value as an authorized user but on an unauthorized path', async () => { + const unauthorizedSetDataRes = await setData({ + options: { From: ''.padEnd(43, '2'), Owner: ''.padEnd(43, '2') }, + path: 'foo', + data: 'bar', + memory: authRes.Memory, + }); + const unauthorizedGetDataRes = await getData({ + path: 'foo', + memory: unauthorizedSetDataRes.Memory, + }); + const errorTag = unauthorizedGetDataRes.Messages[0].Tags.find( + (tag) => tag.value === 'Invalid-KV-Store.Get-Notice', + ); + console.dir(unauthorizedGetDataRes, { depth: null }); + assert(errorTag, 'No error tag was found, should have been an error'); + assert( + unauthorizedGetDataRes.Messages[0].Data !== 'bar', + "Authorized user shouldn't be able to set data on a path they do not have authorization for", + ); + }); + }); +}); diff --git a/processes/tools/build-aos-flavor.js b/processes/tools/build-aos-flavor.js deleted file mode 100644 index cd9a807..0000000 --- a/processes/tools/build-aos-flavor.js +++ /dev/null @@ -1,178 +0,0 @@ -import { exec } from 'child_process'; -import { randomUUID } from 'crypto'; -import Docker from 'dockerode'; -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; -import * as fs from 'fs-extra'; -// Import fs-extra for recursive directory copying -import * as path from 'path'; -import { fileURLToPath } from 'url'; -import util from 'util'; - -import { bundle } from './lua-bundler.js'; - -const execPromise = util.promisify(exec); // Promisify exec for async/await usage -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -const docker = new Docker(); // Initialize dockerode instance - -const args = process.argv.slice(2); // Get CLI arguments -const pathArg = args.find((arg) => arg.startsWith('--path=')); -const outputArg = args.find((arg) => arg.startsWith('--output=')); - -if (!pathArg || !outputArg) { - console.error('Please provide both a --path and --output as CLI arguments.'); - process.exit(1); -} - -const pathToLua = pathArg.split('=')[1]; -const outputPath = outputArg.split('=')[1]; - -// Create working directory and copy AOS files to it. Set random id to avoid conflicts -const id = randomUUID(); -const workingDir = path.join(__dirname, 'working-' + id); -if (!existsSync(workingDir)) { - mkdirSync(workingDir, { recursive: true }); -} - -// Copy the entire aos/process directory to the working directory -const sourceDir = path.join(__dirname, '..', 'aos', 'process'); -if (!existsSync(sourceDir)) { - await execPromise('git submodule update --init --recursive'); -} -if (!existsSync(sourceDir)) { - console.error( - 'AOS process directory not found. Please ensure the submodule has been initialized.', - ); - process.exit(1); -} -const destDir = path.join(workingDir, 'aos-process'); -try { - await fs.copy(sourceDir, destDir); - console.log('Successfully copied AOS process directory'); -} catch (err) { - console.error('Error copying AOS process directory:', err); - process.exit(1); -} - -async function checkAndStartDocker() { - try { - // Check if Docker is running - await docker.ping(); - console.log('Docker is running.'); - } catch (err) { - console.error('Docker is not running. Attempting to start Docker...'); - - if (process.platform === 'darwin') { - try { - // macOS: Start Docker Desktop - console.log('Attempting to start Docker Desktop on macOS...'); - await execPromise('open -a Docker'); - - // Wait for Docker to start - await new Promise((resolve) => setTimeout(resolve, 5000)); - - // Recheck if Docker is running - await docker.ping(); - console.log('Docker has been started and is running.'); - } catch (startErr) { - console.error( - 'Failed to start Docker on macOS. Please ensure Docker Desktop is installed and running.', - startErr.message, - ); - process.exit(1); - } - } else { - try { - // Linux: Use systemctl to start Docker - console.log('Attempting to start Docker on Linux...'); - await execPromise('sudo systemctl start docker'); - console.log('Docker has been started.'); - } catch (startErr) { - console.error( - 'Failed to start Docker on Linux. Please ensure Docker is installed and you have permissions to start it.', - startErr.message, - ); - process.exit(1); - } - } - } -} - -async function checkAoCLI() { - try { - // Check if `ao` CLI is installed by running a simple version check - await execPromise('ao --version'); - console.log('ao CLI is available.'); - } catch (err) { - // If the ao CLI is not installed, install it with yarn install-deps - console.log( - 'ao CLI is not available. Installing dependencies with yarn install-deps...', - ); - try { - await execPromise('yarn install-deps'); - console.log('ao CLI has been installed successfully.'); - } catch (installErr) { - console.error( - 'Failed to install ao CLI dependencies.', - installErr.message, - ); - process.exit(1); - } - } -} - -async function main() { - // Check if Docker is running and start if needed - await checkAndStartDocker(); - - // Check if ao CLI is installed and install if missing - await checkAoCLI(); - - const bundledLua = bundle(pathToLua); - - // Write bundled Lua to /aos-process/process.lua above "return process" - const processLuaPath = path.join(workingDir, 'aos-process', 'process.lua'); - const processLua = readFileSync(processLuaPath, 'utf8'); - const processLuaParts = processLua.split('return process'); - const newProcessLua = - processLuaParts[0] + - '\n' + - bundledLua + - '\nreturn process' + - processLuaParts[1]; - writeFileSync(processLuaPath, newProcessLua); - - // Build AOS wasm binary - const buildCmd = `cd ${workingDir}/aos-process && ao build`; - console.log('Building AOS binary with command:', buildCmd); - - try { - const { stdout, stderr } = await execPromise(buildCmd); - if (stderr) { - console.error('Build error:', stderr); - } - console.log('Build result:', stdout); - } catch (buildErr) { - console.error('Failed to build AOS binary:', buildErr.message); - process.exit(1); - } - - // Move built wasm (process.wasm) to output path - const wasmPath = path.join(workingDir, 'aos-process', 'process.wasm'); - const wasmOutputPath = path.join(outputPath, 'process.wasm'); - await fs.copy(wasmPath, wasmOutputPath); - console.log('WASM has been built and saved to', wasmOutputPath); - - // Write bundled Lua to output path - const luaOutputPath = path.join(outputPath, 'aos-bundled.lua'); - writeFileSync(luaOutputPath, bundledLua); - console.log('Lua has been bundled and saved to', luaOutputPath); -} - -main() - .catch((e) => console.error(e)) - .finally(async () => { - // Cleanup working directory - await fs.remove(workingDir); - process.exit(0); - }); diff --git a/processes/tools/constants.js b/processes/tools/constants.js index 180f180..f776ca3 100644 --- a/processes/tools/constants.js +++ b/processes/tools/constants.js @@ -38,13 +38,13 @@ export const AOS_WASM = fs.readFileSync( ), ); -export const BUNDLED_CHESS_REGISTRY_AOS_LUA = fs.readFileSync( - path.join(__dirname, '../dist/chess/registry/aos-bundled.lua'), +export const BUNDLED_KV_REGISTRY_AOS_LUA = fs.readFileSync( + path.join(__dirname, '../dist/kv_registry/aos-bundled.lua'), 'utf-8', ); -export const BUNDLED_CHESS_GAME_AOS_LUA = fs.readFileSync( - path.join(__dirname, '../dist/chess/game/aos-bundled.lua'), +export const BUNDLED_KV_STORE_AOS_LUA = fs.readFileSync( + path.join(__dirname, '../dist/kv_store/aos-bundled.lua'), 'utf-8', ); diff --git a/processes/tools/fixtures/aos-cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk.wasm b/processes/tools/fixtures/aos-cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk.wasm deleted file mode 100644 index 74b9e5a6a5c5fcfd629f0eba199f3e9580141bc9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 731229 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

ghaBp*JT!6XKUzf}=(v*pQ)bCS z=gN{N@~kX*ifN2-U?)Xo$+J9k9S5AHXLGPE6#G<2@dADkoGuf39u6fvzgs8cbl&Qe z1%gN}9}h8YK_nGQK!n={5lfWaK2Qn|m<`(o36=tARtXTrho~13ZzeFR?bi|{K$XV2 z)`Tg-rTYt*g3w~-Q&SH|*NZkoqy|A)S-wuB=C4hDHUbNwhFXI!`3ZrrnP)?87=ibp z{RuTpS{s=|&l!j_C2e~sWGEmN*m(j$j)M%*&Vf3E#ndb(ic!Yg75Y%Y^QAp&Bvaoz z&pAT)$%}TnX5*4PE63Ffj}R@&YUQ{*sZ)HWQb5$kMPWhk4M;hVgoVruFSg-%;`N_( zoQQ(5)Je7Rl`lHnCe@|HY7!g${W7Y>qaa4N zz~kceMCZ!K-WU|9zSc?0EKpKNi}_WluJdY!R%+OS;!&IEmMNqzPHL*F;r)WDtBTj+ zyfy9iR1wvGJPp=K!d@NLE1`;cfim44m`v3|xkCTE8l@6R2&8ldG75N3@$F3BdiHYZt)GV`!NT6T&pOM&?!x}`~v4!qn_BBW^MU?Uo@ zk9c-<+oW(s9p~z{4z=8eMW_%ak^y~E;>#!9YWo0}teF;{R%ysmBP}ruu%O|gkHT6I zYNv93v|--u7I%2+)%_TSFUD0XWl8 zHGr&nSjK{Qs5W@%Q8>wxGL?Qu=>$D_$TxFZY1VHt=;>*tsH2t0FH5vUbu8an$3c5j zagbKZfm%^73ZytGJCWsE--`dSOB7g=^t}C1Gjs*d$=?N}TFk%GQo@>g3&Nc3Kp9eH z7RbqVMI4^KwR~G2Ws2v?*b2+RTO^hfD-#b*!^v0+Vgs%LZXWr{BV>9{A( z!^LH|yyqqT_Gaz&aGtu94(Z^Y^g}Klat<9FAO!$r6qEAYp|qWSB6S7Kl z%mH2QRWJiwo7X@$sO^|qpewY57gWDhxR2W7y*3%8b;uZcRa+GuwQaH^eCjjWqj@SK zWm%=DlLe8{CEbVkWTUnVa!o4as?U(a>$YlbEI_V}K(4AoOlScvu0^8|EVY3g!8Upb zR?oqD*hgbeo>YkT6?Q|PLR2;>dgU9wnCs3q?6OzXRHqZda2=S@CkC7~RO%B()&Un0 z?%L96j{0ejgcX6TBAxpHOrR0e8n?bSYZ#3WI6YGZJ_AWs^vpQ;Ls@?*CQHeKWgL7S zzR@@!P|Y_Ou*#6&Fgc$xBtT3x-{J*H{m*~$%0f#Qb)#W1xfclde+yvyLORC1I6=xzcma!3R*B2RXqMMibd-~34Qb0u%{AH zde*qen&)Gs`Q2YC0LYdOFZ22pyxmoY9gStS9NWVnRBqYL!xu1`g*D*PBu7O0-xuYd;hPgU73}R8~ADBznnQ~v8 z5Jka7fio#m+Jh40v((idtg#;h=(w;tQ!apJ@x+_02yLcZMDR(PQgYn{zjBJoXX1fe zI46P^$pL50=|S0LY?08vrV}?0^d+_V^0=}FE!1goCIG*DNq4P5%%qBU0_C6?k`?Qv;r+J@wQBa3E$kHpRGd z7J0SgtWApFj<4)7Erk<)N7v%3`P^I)uX|$!d(CL}u94Q}E+fEAG2p-60iytAe1%A^ zE{e9M10$gC&*8nQD3dE)Z*`yblwG{D38AU_R!P%ALNR2m1AW8>$E>=U)Kx!woQ|KB z0v=7dAhzD&mqvx-)(uy31YQ zLm`DfNt1N`#x9k0$2fhZp>j}h5}dx;Wt3k_5Ld1Qi1+dq4$abvKyu5xG%W^@x<>&8 z^%u~1916I=Y1b{x)VI@$40COQp(vpED7u?e@D;!xso=)ZyGC|MYKFGFvj@5M;(*Lp zj)pLZ94K===o!5-^s|fyB=Z!nlbU6QfA|JVKe5DA>+(PhGE`SkZP2M_`0y4|&q_C- zO?6o;1E*o{I_Kv7Ep`I$o16AqV2gBdfvFjU|Dpek5 zyir;hJPxNu;D>W~Tvu`N&xcO45$QSLq&Y?afh2xVVn8nJAMoCPJ_Om`JBFSn2c%bK z%tP1hc3%tQRqp84`rv@ZY;la8ubHdVbD0DJOKCYN9yw|+M7!?f`v=p~G-2INbC zF??4Kml{XIYeOiA=y5;CfPJkx8!%jx7>4O-q}ULy#zHzzl~rC zE59Wm?Yw6N?NG+t_e#A6=rsi6VsmmX;lriFz-<|*UjOCUe9dneNN38g{lANT?I)v6 zPvdKD(>{IeeZzcBds6m5{3qEOZ0UXX8~D-MH`JQnzOM`Mvm**@JCcj74lCG3`14|I zP!@ryB1R)lXwajC-_4IE_phT#zm;DV9DlPYIMAWWNp{=%IfC$e`jwOHw(Jui?sy*G z@muzFyN;?|w&OO_bM36ELJY$4Ti(&myM!!$+q-Ii|5p7y3HhE)LGdL3s0*Iej_2{Y z|I+4jtIOV$*z)<4ehya5_BK)!;iJ!$3`YeqHPgd>P1*PxM`?V`oW{i^LH=*)1D6lQ zr)+MbZ0>X76%($x(pe4PIS?y0*!K)9GZ3g^nc=08Use^gIH3Q@FN=Y+frEAg40c*5 z9s9jHNdvXHNdp!8sH+VwQ_g^v3|o4Dl6?3j`-n%h13kZ%pdxzjA@3FYnug`6nKl?L)&P`SBr_LtOai@T6L@#3HWl;J>6O(DXm;z-7bU0}-Eq07>5h`13Q6j4!u6WHQr5Th2@W{#+ zgL}sQ$=_tP%AzImf`4*J)|`tMq(MPObaUrj#2EAJEhe+^4(P}sb!h}MS|zNK zt8=+>Km1zFv_eg#6;^r2ShHUxyttXv-fwW5tMDRjow*WqlH%b(r&)V;ZZ@z9hIrT*KjEBa~+vm^|>@YI<>MRp3hiE%JdiE{PX5}tpZCS zocGkZZX8w1qVjsW;OpMNz~HmjOFwKWTuUz^1=XA%v4#y}d?~$N&cv+$9()8ligA(; zG0E(y4VX#S5jd6yQih+L2Uu$y)CxI3s$GpCt(1>MjC0Ng+iF$`fmhBoZn&CZ=Dsyv zQ!#KnSJ2n{tS_i;ezmo_qm(1dEz#%cpxQ;!z(VEo37w*Yva-{2B8y;K{Yjq8(kqG0 zNo4IL()JAW3mi)o{Q`|b`UOfy#zAUEAh^I@AHlygKSr{^as#Og7V{i~#FiGe5#1Vw19tPtB2t{(;h$&lA&l6a34bver~wM1g;!6Uzr}ZfOLGynE^Otoh#M zF$V7`UL<7!ung@vNEcKM^28_sOZ{9JiF=c6A#3&mkTgszXJ0C*5K;J012_6_HM{tDnOm5}?1-CnA=ckzu9b9=)Zi_7otsqjA;yPgAU!44d$>!_!e0izvnYgAbFTq-)g0H1QVPlq zxe)+r(#_!6bI$9s#)H`zB1%?x z(9>KxPB18VE94^~j&WUi30k_YBEk}98>*9+>QLRBb$C5`ATr5?P^`t={nM?rbU0RX_0!L8F+wc(HFdCK)lZ;xx>JN8IlGc zS~tYwy=ZjRbI5+NszLAHC%r=ceKNL7UZqp>@2}yquyMWNSAddtymfZAULG88B|p%9 zDNJ?ifA!*Q@g-l_FXDGZsm1Sz$-5#>m9metB-WNulMP;t{7N6G zg!A%xDv^H#n4%^eRAGyQ)JSB2a_jjeqE%d>Ext~QJ?>a3oTuD=+?>;_WOU}TQhDG` zUEmtEVGn!$@*{AU|Y-@t&kY?lR+#Rt92*~Vx>OdNrKqjaM-a{t16V*Fk=?UD_4 z^jU0Q0R-VoS9l!@Oe`Jo>|SaO2n^3@FAIcMezf=5KzhL-UKYsDKkraiu+;a2lYrev zadBIju6Itf3~0d(k>QzyjvSneX% z@1wS{ExO)8dhg6hc(fRpd@Cu`f?65w$s$ykd)|k!c`F}o_z!DKY5TB?DQ4{BhuOt= zLQqtBj2`T@9xmfax9)9mmn68%MX1>LaiIuR9JC3)Sqkc*a-w|#5m`qn^ zP)C<}sSTI8fsXA!1vXqZ*RXxYX8MQ!3C;9Wl+>$Vm9jTjqh0!jIT#BlWiA?;iDxfy zOQs8pmJNUXTpK%sKjV8efCIpIz1feD}KuIjENRd#K7YT80K z-GvL@hNmW^G$!Jj(wLAQXA$MJn5Wh^aqmSGM&%;wjhAHK3(;sMs8PA|CW{xN%+BG( zJ~1O3K$}tCQ8_tyP|TpAZUsvpup-S+8!+9=pIV_pm?qlIV1@}n%<{0GveJP-_M%^- zIB#C*W_*ZUKcnIp&Q`**rp5+cI+Sm057Fm7^*bd*DNimOkl#1U$&GkUi(`=s8)_qM zE)2Z1HmOJ17PynVKIhRbxpo!C6AXLM0| zH;Ox742FBhMoAS^(d`a+{;)diovhr_t(`5Gu#YO>NQ>LahGn5nU`Be)c0NV~f&x-hIJ@MED>q&^IF zh~+B1^XFr-#g8A;SiLfdV|jrgc0;mnO&B0!Qj30o(9s+yhA3@F`txC0wtM!h&ybG{ zq>Qd-UEK5W*y3G}2Hf)@*`7D$@-8!u%G3l2KJ77b-hpO$Y^7`vuPW2yXGI3;BeP-h4_qi;K4ci?)^STeclKk+h5O)>oXy@`+x@Hpw=oVUb z;;qnN578_~*^OUbaZ~_0*w%UA)nyjhsibS%B7t36SzVZ!=TivsBPn*j3E z+%UGmh&-vdg_eYUCIJw<8rfXjOSOy$oyg-}ghFRH7c`Mw4!3Nr1^}=?T*+I&t*VbD z-3#&J-kpbAcmq6p8StY_l9hxO)q2v(^(IYh3N>(ZT|1Y-OQ{Adv0nPv4Qd+`PM!{->^aHI zAXW~MS7%h>Vowz+k-+v;oa5kg+E}mR@wq=&9G)YoCdX_@_thVF<%ryma>RFxAByQ#StEw-XB zo%G?* z7F8>fOclLyhNN~OiUR8j+M2O0mlNfxDmm80YOugURA{Xa)`cS@W#zkAS9sYO5h2D) zMwDY+{ayZpf7M)zVqJWEF6-*L{YuuQwok^o<}fbBlRWFnDKa5Ww#Wa*+>+6PgC|_c zl6`wmLX{}mif7@7Sjc-(GB@*H#0xJP$5AmaGw2ilz4AD-*z96sM7JfrMNl_R_!>M6lJL%L@e)yT-BvawdxQHu{_15djvCXuZ4}qr!O~z z{sj92=L>f7gQ<_}Ta+ZTAW1S=y!(Y&OaTh=NN|$iFtq$&YA(XYr#?H{Sqo*z7jt}| z^%j!RyHE7x$>oG@k`4>?6)eixgCy=1WxlfEBqo}h7s z*zNaUMtoYHlBhi(K@p*aMXkOz+FNLX%5a^je%rh)CHrNn|E7E7VUB20zGWW9I~*Qf zhG!I8lJ+`N@lCnvLU5t8`@G};lw3>^dcJ`N$k)DA&lSg&dUzeZ8R+zHSH=`=n&_qd zcihdiwi<0b8$3SC1N1d19ztYY&hcT6mvH%IF1*$Ju=FWrI1?(-fFMh z{iy02DEo1n1m~5)Amv%!*etlEvk2h|qNKR%324=tYb|yxX}LbRwS_l>?zn_*%;dk1 zy{Zk+qhf;#f|(meK6tZLZpgJJ(h>u`gE2bV)eC~K2ZTC*B(d|{-SlzUw<7$DQvydjoM zhYP6h?bKy6MBD-Zz?|AD1ZNn@h#`5+BYB?qk&r6mM}o|!S90Ac>1!-grT}(ga~oy+Qtve3|sSZzsbsRA42dqMKXufqOzHt8_+@SBDSXPLgqG5zyVr z3Km9bqJhE%oO4hHRIH0P(1k2ZjNFxW!hIbwchF-(pH3zOqU);Vr7~YQju_j{CVp0r z0a5cEakO=r1o!HqLC?|NHS9j(zu1b`;w5Grw`;$Jf8{14lT$X4C%V z*F5;BuI~J$@1!ID`3rXS8vXa_jni2)Q#SR+w?FSApT7U2H1+EEZ4C+9ObKbJ{e|cd#%V}i)vBSch%{Q|%FnhBpw@|=*An&6xBRe)wCyN@C%9v$y zfN52>r?=zhY`XgI+kdw)i|%4J^@RNw?J*ZP-uNd^MAuHNfMLqXP7~jKN;|_lX~PAc z_`fn=IWn{9jvxQU zmFN7$VL|%!cijAY-@o^xI^BNSv!43Q_D|^a;qTo3p7*@xQBE-wm8Blq82(cJ^%ovK zKsUZKbj!Mz{mKV7^7IuK9-aP$p^xbF7qgdt`v1P@KAoP}6TN-irJvI2kWKs-Ic+0C zO8^@VK+w}O2Unm+lR=;N6-V!On|~UIVSCNK;<&qVFpCZz%%eMa3u?QH8i7nk^Y%D# z)%x4@00-vTM(=;Kqeh+{%xm4-wS!rDP@+lyHXT^`*y`!{M=ze)^zW{C@U+)GcB>BCpPcj~fTZ~x@<49`P> zGwl7@{L<>QxogGleij3_C!FBuTNece^?<|IT-&A^#xC#t=nD_Hnb)1G1Sl;=>-~U-S{fFOwY^sJMsJCw$cJKJ}5$oIM-uaG0zq$O|bNW_Z zRo~JoSnN&fn}23b-^#0cbEg~Ca%Slp>ILrXxs$WH0-@t`df}#wt7@w@7ecEm5IR1m z7yeBLp&bE2KYHjJzgnXVnaPArdmef8^&5Wf8zr;2;3a`CUGUI!etJNGBQwlC3NDiTvz6p0HLU-% zKXRqj_8A~#^`q^zVs+(*HvYWw=xJk1wGHrd@iv6bYcqNrf%J4_d1vgFd^xh9I9GCD z#rCb8VG@SyDSiD0(FkLV7$e>J}wn)i(qvTVTII*|2pXDfe4X+dt%5qfPi!zp?6>1VX z??$6p-buH1#$OMA)0fDR=j_JUWy`m;R}^3P;0VeGb5%RUq~|Nao_utRE0!{pjc#qX zn1YpC4!3LcA}waMC6m6LwN!9(NBRAF);g@BddEI!<(Thliu|?O}SS9?}_}q4jL#;r3uU3GS>U@LQWK8*WFMgwd>ih^8u6HCVbtCZGp}?;W2z zwAEH!Y3pyafOZr*Fz7mfzG@>7u5wD9LG)MM>O4PNv9&$k*`UK|+0bEf376l~R+m?A zZLiLji^!5mzM5u3_b?-F4)_32eRcWuBc!%8|fq$ z4~22CPwnnJwRsR(;voR3+n`(G6m(=LTmEtt>MM~AOfZaN&{^vy zkzdx-h6-7W@9^BPqdx~AaTO351m$rVH1GkFV@2%wj_u+gyG^J}aA;@s;USS_K6H=A zc5#Q=A=t$|EI${Mpn2J3)RC{S2FR`)3pz4%ESh&YsPo8~e5#!uuTMhEMuy^*GOw+~ zF0%=|I^&SBP(JU1GDpvf?7wZDUjSVL5S}K1=EGsf+-*xEPy?Q%DzFm#vSBd8l7j4x zO~B%k!!T*My`r=*HfAd{xMy_-_bgGQSdky(?5%y^~Kjbh{g3>*;#iz*%}@m zzHQ_5G|)}6Xr^7u5;64}zv?raE;T#s(>#hy!boKBS=b#8!!ARL&K#V{;)Cpa?w{Q_ zGkvgmVZttZvb-=}{hZA1rj1R%_FvYl{Dth@v(s$8*d~&bB~CDQaaA&0Vn*%11f7YKn1%ghUW8OBL zMC5YQurYqUhFF5}UG4EKEq3gIRnA6S;Cii?L~)>MdOFE3(k$??>N;oS4=@&;;ViO* z(Dex^p2`X3TFWJSafU3^uFIT_Yvs3{;fmIRY_4T4VU)9VE@zap^%MYfch7dEV7Ruy zRfFN|TnfUtyBD~GQm(y-oKi-n+;B!ViEPN~*BPQSgzJamp5Dhr%|W0h;a$_nMbT+d zySf54{YGe9Te;+9MF~mzPaWkKMuHz_$qOS?0iC5UjKG=RuY}&De8cr-q4}_N?_%rC z|1U&{+kMQh`RBn0+@%@dr_4Is!Tp5NHC1*qaR~6DfUisi^ zea4$N^SW^~W8!OMo%d@qiDfNC&F}^payl}n9)NXvZxD8#WwM*oT zYUbw(f3Dk(2)8c%cT3bb07NCugC+&xlR6owhAmO^mjauCi+y&q$M(QK$EDU#n}B)d zi^6JGw*ebD!D9GsW>EBLE)OTs&!Kc{+`Hg-0zd_Rs1T?7f)sXVLVu`O0#=!1cS` z^lAP0I27qJe-gn^2JG z7t`e-o%7%RjXRI`VC)O+ufpiY%2fn8!w3)I7y&S03ZmCcD0|8Oq-Dx^W^vj5X7h=9TEmLd zcClhS_D?(Hf(ebPx4Yi?(b2cP(Y)QIx8THyUjOzGOZ^Qo%DzZ`ZTjvDe)LrFynU1S zVB)f8$96OEWTij!mB*h-FU7-F?|kIbpE^OEd(#};mKgO-x4h{8`ZwQw!GFDwDbbjY z(GcEyR7{+P<=qFWcQ;Nm6K0e?^~Rt0w->McVdxQhrmO zWH6%q?96miphXkFDZ^j4$*k=W{0)W>W2WK%6vr|GZx^M2D_ zW*`vHbfWP|zMRe+yi@MhC1jWn2%v!y110FTEI1b?%%>=3Qe!ssg|Xv(?qKio$RxpZy~ zxk@@?s3C_|Lr3|g;`kiO8gj(dmQ=#RVtA32n@4gG_i`D3h?CxERNWs^q7g$mQLTpq7>AoaWK%A-4 z9^nz65aycDJyLhngdRns$r4+3AKLQ4?r3gXmIy0M(=x0ziV0pobdtmRynmsu`b;SI zRYt9^vdC`Xud1DGxxUFpdELcNiL1*MzKW5g;9T{~S1<_Sx4|LfUERJUZye&ffiI+6 z6*Qj3!x$@N{S07$z;qyV1A(Dg2jDjeY^QsRjo9=={{lm$bJTFysRjCHrI&ZV>(_F! ztddyQ@Mu)l2Jcp%q)Q7#Qgx9iwGONl5j5Q300!%07GIix^>ls`VgI3VX_2c{mauYk zey=vwp(6owUY`7CQAp=+BfhM=S|;q(z9gFPxFlR*b!Ln|7CvPpOMH00%@HAE=5Ze4 zXsVgO07hS6v7Eex7RE0KL`)gJOSN>`L)*2ubZ575E!w|MAGraqjzQWfj&Wz==FXXK zFd#3!6CSw>0x}sL068S@EK`iW6E*)xn*F@}y%tD`5+KP?6yt#i#du)a;zp+|ZS5D$ zI@Ka!eIL@%klm+Nx86yPPv8}hs zB{4d7%t*j2Vlo9(`eB93cb4!$vzJG}?IQT}vXC(|wYv++w7y2eylkk?DGziWKr5w* zy1OO#Z4$=e-I;0nm3f zkTs|o)Os6hiQyu=F6)p4AQ|MCx%dufn>{1>jZ3TlB2I-<^eVZ6eKtgL791ee1zvJa z#I{DBulaH;aqC^$e#HwG__N;#i2i~?uVh0`JPeRf?TR7rod!Cs?Rd1y{R#;u$!e94 zl-}6hNL1p{79V1KbV=I}1wM*mt>)x2taWVn0)B31-LZ3=5-U7x>XaRV-v1{E6&nnIzCN?RPkoHJfhv7fCX8Mg`sfP zzC5+$x)4*FrzJ;HuInL$h_QBJ1r^t%qs17GWLUtH=(Ac%%q`^go(OXXogquFAdv+4 z`WPe|l&5PET^=&=8PXnxnUUnmY{d>_O zXi_E+O@eo7G7U{O)Fjko0}V|g%Cm-V(k)9+RANuCnAD`DFqt4S5(Y7j7cmWs@4)eN>VI| z75P#ej?>p*v3NhAWMkRB;vf_bM9IJWEQw2jj$!(Bpy%`kUvtORab7%06`>%>h9F{P zBL`#4Mp&XRa;xX*4L3qdW=@Jpbj5uZdqMByaQ^~w*y%T>=Ai9J8eNyCF7b>5>H%Lg zDNU9ca`ju1tMi~;S!kV<%XycHas*~f@ys!|Ixam z2B+0+kOEphD_x(bR|u=uD`(ZbYvjLa3@}EG29Uxhjq{b#x72dNR7&=mikgIpMD`|g zWp54HTh=slldEkz7N1$BM>C7aa^754T*#FR%PdPQu9XJU{8kE9&s%m;g)>vyrlqrz zS{vLmjYWsDQZ03k@OX8qWpNf*2L_p%fX$HvuSAsy3QC$mxG&~O~7 zl-5REyjAfJecgYXa*jeLtu$v^dp_UoJm^t&g$V~egcI`D1|(2Nf&dOWm+wodZIuF% z&olih#2jgY7q2M?Iyyftq$}OQ6f5x4Ibb2=IrIGXI2GkuOoMkHK$v+ElaqzJ;^G=n zCh2x@g;T$V?BNIlESTmmnV~Yv`&>G@caPRV4Q3_kOm0!3i43rdLv@~NC@PI~(Mvo1X&r<{KfEbyIam<8@&h-UV0qor_DLam;kMLMOcpqgxRc#~V{L zk}`Q7#6@;=Vj|okX*4^DcFwe1j*Ms7%OZ9C!E%A{y&Z2${D2kLp=rMk%T@F;%Oyph zE-jaG69YAMd7k?H8TI;tmW&ydbJlhVq*;nl3Cl0>29|F&uO>~xmYJK&V>L0qq=0^? zc#Y7bB1?S6I`hpmZq>)aso7V15+=0%+>|8McU>H?ZQWg@QeF7RPo+EtZWQ+zHcza?x zO2H1IYKgM8Qdrl!Ul;Jp>20a=9kWc12m`9X(8O(t`@<__)z`_YNg*pRh_7po$&rIbps2xBJXEl=%359K=t+t^r z2HLKBUad~HF||M_uTQn?m1X|$zr@HbeQ zLh$9`1+~!i^Bp(N(SRFAjs^}HCMSm_uuiGr)VJNO_0f!Vmouo%dIB2ItDAnz5>9|J zjG;Bn5|7Gc*PIdq0YPh0^W7TA?xS5Urbj)HY3d4@jDqpyGJ`;*DTC?avasu*5xbJk z47ni`&&Nz;me231vFoMt0LA}E)sFe@;)>Q2B#&0V(}c83%&#SBm&UsV7^`0edcP93 z5s*9KMto7J>_~OwKdCw~f!FD>$uwZ>&r}5h%$dpe4&`onJp#x{ps|U}-8biI_?`sO{t3 zX-vQsDj0J?@9J}FWb{i3a2skY?qlmp9OH)`r#)h~q>!HaNvCB0Zrw=-0f^-*2$gWLF?8)18^THl5WDf;_dEV*l3_SIrwU zdH-~-%C-EP$?G%N+V-z_+#VndQyleYQSpd7D`xFT&{4PVFK^%LZ{Jh1O_qyZ&6vISG!hu+@kR{9w)>xm|)DyWu4>4+IELr!aqJH2nU-M!7D|eeOS_n^{wcldPN%6$jEw(%jB$MKp`vHoj;r(0p>^!17Kd{FRa{7cn#rrd@sDl16 zSu+q|R7`oOJ!xM8N>Op#eYAMOUE*4;IOaQ2FCO=25fj-kXimdFQKtVYP5XAl<~VJG z`0k$z^xB$_hT^?>Kaknz()>%b0^%j8t#g8v79#*vfvTXy|G)i0QpVP z4e!5vbkm=*@&--9XoHeu*7@z7-+Azk6Dc9bY~S7^WyhyA$>j_a0|Hy`OTlzbFY3!{?2um{)tZCVX0`} zgr_k#nXFEKf^TKE%mwbGG+VvG!x+5ojX2Xa^Npnh@oCBY895K}s@xhR z09mpRNjY!ayZ^xS>{Ps~-IroYg`biipyZqK#?Gv=N3uSBubTF-R%tt_o48hlcOB)h#NJsC%)Aw+Ow=IOElb(rd57u) zw_A>X)p6g+YwYHS?_qAUhJke@tZn!tD;!pIPV9P_DAe5|Eqle*&KtK~W=ovqfUO0l z(%NC!P&hSPVlrC&L84LkI7#MxcZ%aDO{AX&#{l0aRe+|SSl1mCfaoirEp?nZTu~%oN-5K(Ye| z8B9rVv;y6LUWmd5shev$NUhFC`$?O_oml8JiwVR5aq@*p&y(2Zp$?s*Bq&84nqQ4^ z=Py>ns~a}K6M!Q_p4C%1@Xf7Oay8@{-3nnT+h`qx(gG{okQSIKeV=7zVwW)>ulC}O zM|6&P-jUz|F^^gEkz}DL+*sHD(e*dVug9+?)YJC^EZ+H`A`rY50h_3Ge+`vOR?Yi$dnC;8faFal182lpOo>-dUfxb+?@mF3=@fm7yJF zQD?9cFu)X-+eBaN3@Ut4THCg7`{Iy9)p7wKoVK99Jh^P6VMUdSF@3E0;n>-zNiqqR zP4(MW^B$4O(-SX5wu!X~9k-GYDF;RMfzFRtWiSg4mrZn@V*R0&Ac(OD230aT=Z=>dK7o?_Xq zV(NECcj!r#)$vdE&3Uqzv9?rP$3M}S`^48?%qWt{bW$B4lS5kN;6;FDu=9Lj!zzWC zq=+ZQDmgRFg@Dho;6;!q{IkBXk{P~Pb?hJxxfGy*9J%L zDfzk&1vu5B4vg^RGB^C50%(0)KvgQ9VgVX>$G&Bx&piRXGki?3==wI=1gr~HZ{qy) zIQBH)wTe?YDQf4DZo|#T5EPdHLs#4hrcj&^t0N(g(-#pR$L;@Nrk*z(DOzNMzlQ;4{>wRNSpe(aG zN#-KMKIAPZ19Tov zS?hUJzXRzRLE3UZoL1{M=*{S^8ErO^FY-H)q1r}3JH^6g5t91)I^ z{n7z}JMW2KF>x5LK{565(H%b>Lu7Z2l7d(~{yA}LMFWYF?kXbO{l2?gwv9&3(bAdu z(s%!?Z%ZDvJ}gg2EhDo)Hh&AB0I}a?XjR;hGr->%hKndR>~6@s_cun`WZr?v03zA1 zs$?W2b&{J8!_BkCVHV&q;C?$oG&iwRCRZpHb4wf7qYle3xf%cg@>T%qmb{;Z%xow` z*@$IZ@I|JF^mcu5V z;U@e}OkA$)zPBUeC)8@=tH)_v|Yz@<)*k2j|itGW-Z$|LIpd32!@2~0?z z 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 BHT3`h diff --git a/processes/tools/spawn-aos.js b/processes/tools/spawn-aos.js deleted file mode 100644 index aeb1b2f..0000000 --- a/processes/tools/spawn-aos.js +++ /dev/null @@ -1,53 +0,0 @@ -import { connect, createDataItemSigner } from '@permaweb/aoconnect'; -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -const ao = connect({ - GATEWAY_URL: 'https://arweave.net', -}); -const moduleId = 'cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk'; -const scheduler = '_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA'; - -async function main() { - const luaCode = fs.readFileSync( - path.join(__dirname, '../dist/aos-bundled.lua'), - 'utf-8', - ); - - const wallet = fs.readFileSync(path.join(__dirname, 'key.json'), 'utf-8'); - const signer = createDataItemSigner(JSON.parse(wallet)); - - const processId = await ao.spawn({ - module: moduleId, - scheduler, - signer, - }); - - console.log('Process ID:', processId); - console.log('Waiting 20 seconds to ensure process is readied.'); - await new Promise((resolve) => setTimeout(resolve, 20_000)); - console.log('Loading ANT Lua code...'); - - const testCases = [['Eval', {}, luaCode]]; - - for (const [method, args, data] of testCases) { - const tags = args - ? Object.entries(args).map(([key, value]) => ({ name: key, value })) - : []; - const result = await ao - .message({ - process: processId, - tags: [...tags, { name: 'Action', value: method }], - data, - signer, - }) - .catch((e) => e); - - console.dir({ method, result }, { depth: null }); - } -} - -main(); diff --git a/processes/tools/utils.js b/processes/tools/utils.js index 95adeb3..95b2472 100644 --- a/processes/tools/utils.js +++ b/processes/tools/utils.js @@ -4,9 +4,10 @@ import { AOS_WASM, AO_LOADER_HANDLER_ENV, AO_LOADER_OPTIONS, - BUNDLED_CHESS_GAME_AOS_LUA, - BUNDLED_CHESS_REGISTRY_AOS_LUA, + BUNDLED_KV_REGISTRY_AOS_LUA, + BUNDLED_KV_STORE_AOS_LUA, DEFAULT_HANDLE_OPTIONS, + STUB_ADDRESS, } from './constants.js'; /** @@ -26,23 +27,29 @@ export async function createAosLoader(lua) { }, AO_LOADER_HANDLER_ENV, ); + return { handle, memory: evalRes.Memory, }; } -export async function createChessRegistryAosLoader() { - return createAosLoader(BUNDLED_CHESS_REGISTRY_AOS_LUA); +export async function createKVRegistryAosLoader() { + return createAosLoader(BUNDLED_KV_REGISTRY_AOS_LUA); } -export async function createChessGameAosLoader() { - return createAosLoader(BUNDLED_CHESS_GAME_AOS_LUA); +export async function createKVStoreAosLoader() { + return createAosLoader(BUNDLED_KV_STORE_AOS_LUA); } export async function getHandlers(sendMessage, memory) { - return sendMessage( - { Tags: [{ name: 'Action', value: 'Eval' }], Data: 'Handlers.list' }, + const res = await sendMessage( + { + Tags: [{ name: 'Action', value: 'Eval' }], + Data: `ao.send({Data = require("json").encode(require(".utils").getHandlerNames(Handlers)), Target = "${STUB_ADDRESS}"})`, + }, memory, ); + + return JSON.parse(res.Messages[0].Data); } diff --git a/processes/utils.lua b/processes/utils.lua index 7c90363..0d772d9 100644 --- a/processes/utils.lua +++ b/processes/utils.lua @@ -1,9 +1,7 @@ -- the majority of this file came from https://github.com/permaweb/aos/blob/main/process/utils.lua -local json = require(".common.json") +local json = require("json") local utils = { _version = "0.0.1" } -local crypto = require(".common.crypto.init") -local Stream = crypto.utils.stream local function isArray(table) if type(table) == "table" then @@ -361,18 +359,11 @@ function utils.affiliationsForAddress(address, ants) return affiliations end --- returns hex string hash of the property value -function utils.hashGlobalProperty(property) - local stream = Stream.fromString(json.encode(_G[property])) - local hash = crypto.digest.sha2_256(stream) - return tostring(hash.asHex()) -end - function utils.notifySubscribers(data, addresses) for _, address in ipairs(addresses) do ao.send({ Target = address, - Action = "Subscriber-Notice", + Action = "KV-Store.Subscriber-Notice", Data = json.encode(data), }) end