From a4fd1dbcbfc587f0e867b12e7a62230812e0d454 Mon Sep 17 00:00:00 2001 From: yourfriendoss Date: Wed, 12 Nov 2025 14:52:45 +0200 Subject: [PATCH] Add all of the RA stuff --- readme.md | 52 ++++++++ src/.beta-installer.lua | 6 +- src/lib/hmac-pbkdf2-aead.lua | 208 +++++++++++++++++++++++++++++++ src/lib/plc/aead_chacha_poly.lua | 97 ++++++++++++++ src/lib/plc/chacha20.lua | 204 ++++++++++++++++++++++++++++++ src/lib/plc/poly1305.lua | 204 ++++++++++++++++++++++++++++++ src/main.lua | 15 ++- src/modules/ra.lua | 83 ++++++++++++ tiny_ra_library.lua | 97 ++++++++++++++ 9 files changed, 962 insertions(+), 4 deletions(-) create mode 100644 src/lib/hmac-pbkdf2-aead.lua create mode 100644 src/lib/plc/aead_chacha_poly.lua create mode 100644 src/lib/plc/chacha20.lua create mode 100644 src/lib/plc/poly1305.lua create mode 100644 src/modules/ra.lua create mode 100644 tiny_ra_library.lua diff --git a/readme.md b/readme.md index 762fef5..42a8bb4 100644 --- a/readme.md +++ b/readme.md @@ -15,6 +15,48 @@ +## Features +- Lua matching for inventories that are passed into AIL +- Import all items from certain inventories (like ender storages for all of your farms) +- Chatbox support for withdrawing and depositing items +- Remote access via enderstorages + +## RA +SiSS has a remote access system that allows you to access your items via modem. +To use it, first set up your config.lua correctly. After that you need to implement the tiny_ra_library into your program. +You can download it by using `wget https://git.sad.ovh/sophie/storage-solution/raw/branch/main/tiny_ra_library.lua`. + +Example program: +```lua +local ra = require("tiny_ra_library") +local modem = peripheral.find("modem") + +-- modem, port, password +ra.init(modem, 42420, "test123") + +print("withdraws 64 of dirt") +ra.withdraw("minecraft:dirt") +sleep(2) + +print("deposits 64 of dirt") +ra.depositByItemName("minecraft:dirt") +sleep(2) + +print("withdraws 12 dirt") +ra.withdraw("minecraft:dirt", 12) +sleep(2) + +print("deposits the first slot") +ra.depositBySlots({1}) +sleep(2) + +print("withdraws 32 dirt") +ra.withdraw("minecraft:dirt", 32) +sleep(2) + +print("deposits 16 items from the first slot") +ra.depositBySlots({1}, 16) +``` ## How to use 1. First, in your /config.lua file, add the following: ```lua @@ -27,6 +69,16 @@ return { }, ["chatbox"] = { -- Not required, just don't set! ["hartbreix"] = "manipulator_42" -- Chatbox support + }, + ["remote"] = { -- Access your items via modem, not required, just don't set! + ["ender_storage"] = "ender_storage_493", -- Enderstorage that will be changed (set this to owner-only and computer-chanagable!) + ["modem"] = "modem_1277", -- Modem to recieve messages + ["remotes"] = { -- Remote access + ["red/green/green"] = { + ["port"] = 42420, + ["password"] = "test123" -- Required! + } + } } } ``` diff --git a/src/.beta-installer.lua b/src/.beta-installer.lua index 3e98c68..c6d12a3 100644 --- a/src/.beta-installer.lua +++ b/src/.beta-installer.lua @@ -39,14 +39,14 @@ local function traverse_and_download(folder_data, prefix) for _, dir in ipairs(folder_data.dirs or {}) do fs.makeDir(download_root .. "/" .. prefix .. dir.href) - local subdir_url = base_url .. "/" .. dir.href .. "?ls" - local response = http.get(subdir_url) + local new_prefix = (prefix ~= "" and (prefix .. "/") or "") .. dir.href + local subdir_url = base_url .. "/" .. new_prefix .. "?ls" local response = http.get(subdir_url) if response then local body = response.readAll() if not body then return end response.close() local subdir_data = textutils.unserializeJSON(body) - traverse_and_download(subdir_data, prefix .. dir.href) + traverse_and_download(subdir_data, new_prefix) else print("Failed to get subdirectory: " .. subdir_url) end diff --git a/src/lib/hmac-pbkdf2-aead.lua b/src/lib/hmac-pbkdf2-aead.lua new file mode 100644 index 0000000..3a87e1d --- /dev/null +++ b/src/lib/hmac-pbkdf2-aead.lua @@ -0,0 +1,208 @@ +local g = string.gsub + +sha256 = loadstring(g(g(g(g(g(g(g(g('Sa=XbandSb=XbxWSc=XlshiftSd=unpackSe=2^32SYf(g,h)Si=g/2^hSj=i%1Ui-j+j*eVSYk(l,m)Sn=l/2^mUn-n%1VSo={0x6a09e667Tbb67ae85T3c6ef372Ta54ff53aT510e527fT9b05688cT1f83d9abT5be0cd19}Sp={0x428a2f98T71374491Tb5c0fbcfTe9b5dba5T3956c25bT59f111f1T923f82a4Tab1c5ed5Td807aa98T12835b01T243185beT550c7dc3T72be5d74T80deb1feT9bdc06a7Tc19bf174Te49b69c1Tefbe4786T0fc19dc6T240ca1ccT2de92c6fT4a7484aaT5cb0a9dcT76f988daT983e5152Ta831c66dTb00327c8Tbf597fc7Tc6e00bf3Td5a79147T06ca6351T14292967T27b70a85T2e1b2138T4d2c6dfcT53380d13T650a7354T766a0abbT81c2c92eT92722c85Ta2bfe8a1Ta81a664bTc24b8b70Tc76c51a3Td192e819Td6990624Tf40e3585T106aa070T19a4c116T1e376c08T2748774cT34b0bcb5T391c0cb3T4ed8aa4aT5b9cca4fT682e6ff3T748f82eeT78a5636fT84c87814T8cc70208T90befffaTa4506cebTbef9a3f7Tc67178f2}SYq(r,q)if e-1-r[1] block then key = (sha256(key):gsub('..', function(h) return string.char(tonumber(h,16)) end)) end + if #key < block then key = key .. string.rep("\0", block - #key) end + local o_key_pad = key:gsub('.', function(c) return string.char(bit32.bxor(string.byte(c), 0x5c)) end) + local i_key_pad = key:gsub('.', function(c) return string.char(bit32.bxor(string.byte(c) , 0x36)) end) + local inner = sha256(i_key_pad .. msg) + inner = inner:gsub('..', function(h) return string.char(tonumber(h,16)) end) + local mac = sha256(o_key_pad .. inner) + return mac:gsub('..', function(h) return string.char(tonumber(h,16)) end) +end + +local function int_be(n) -- 32-bit BE + return string.char(bit32.band(bit32.rshift(n,24), 255), bit32.band(bit32.rshift(n,16), 255), bit32.band(bit32.rshift(n,8), 255), bit32.band(n, 255)) +end + +local function pbkdf2_sha256(password, salt, iterations, dkLen) + local hLen = 32 + local l = math.ceil(dkLen / hLen) + local r = dkLen - (l-1)*hLen + local dk = {} + for i=1,l do + local U = hmac_sha256(password, salt .. int_be(i)) + local T = {U:byte(1,hLen)} + for itr=2,iterations do + U = hmac_sha256(password, U) + local Ub = {U:byte(1,hLen)} + for j=1,hLen do T[j] = bit32.bxor(T[j], Ub[j]) end + if itr % 500 == 0 then sleep(0) end + end + if i0 then + DRBG.K = hmac_sha256(DRBG.K, DRBG.V .. "\1" .. provided_data) + DRBG.V = hmac_sha256(DRBG.K, DRBG.V) + end +end + +local function drbg_init(seed) + DRBG.K = string.rep("\0",32) + DRBG.V = string.rep("\1",32) + drbg_update(seed) + DRBG.inited = true +end + +local function drbg_bytes(n) + if not DRBG.inited then error("DRBG not seeded. Call seed_entropy() once.") end + local out = {} + while #table.concat(out) < n do + DRBG.V = hmac_sha256(DRBG.K, DRBG.V) + out[#out+1] = DRBG.V + end + drbg_update("") + local buf = table.concat(out) + return buf:sub(1,n) +end + +local function seed_entropy(extra_bytes) + local accum = {} + if fs.exists(SEED_PATH) then + local f = fs.open(SEED_PATH, "rb"); accum[#accum+1]=f.readAll(); f.close() + local seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h,16)) end) + drbg_init(seed) + else + term.write("Move around / mash keys then press Enter 4 times to seed…\n") + for i=1,4 do + local t1 = os.clock(); read(); local t2 = os.clock() + accum[#accum+1] = int_be(math.floor((t2 - t1)*1e9)) .. tostring({}):sub(8) + end + if extra_bytes and #extra_bytes>0 then accum[#accum+1]=extra_bytes end + local seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h,16)) end) + drbg_init(seed) + local f = fs.open(SEED_PATH, "wb") + f.write(drbg_bytes(48)) + f.close() + end +end + +local VERSION = "\1" +local KDF_ID = "\1" +local AAD = "SiSS RA rev1|chacha20poly1305" + +local function u32be(n) return string.char(bit32.band(bit32.rshift(n,24),255), bit32.band(bit32.rshift(n,16),255), bit32.band(bit32.rshift(n,8),255), bit32.band(n,255)) end +local function u16be(n) return string.char(bit32.band(bit32.rshift(n,8),255), bit32.band(n,255)) end +local function pack_params(iter) + return u32be(iter) .. u16be(32) +end +local function unpack_params(s) + local i1,i2,i3,i4, d1,d2 = s:byte(1,6) + local iters = (bit32.bor(bit32.lshift(i1,24),bit32.lshift(i2,16),bit32.lshift(i3,8),i4)) + local dklen = (bit32.bor(bit32.lshift(d1,8),d2)) + return iters, dklen +end + +local function random_bytes(n) return drbg_bytes(n) end + +local function split_nonce12(nonce12) + assert(#nonce12 == 12, "nonce must be 12 bytes") + local iv = nonce12:sub(1, 6) + local constant = nonce12:sub(7, 12) + return iv, constant +end + +local function encrypt_with_password(password, plaintext) + local salt = random_bytes(16) + local iters = 1000 + local key = pbkdf2_sha256(password, salt, iters, 32) + + local nonce12 = random_bytes(12) + local iv, constant = split_nonce12(nonce12) + + local ciphertext, tag = aead.encrypt(AAD, key, iv, constant, plaintext) + + local params = pack_params(iters) + local blob = table.concat{ VERSION, KDF_ID, params, salt, nonce12, ciphertext, tag } + return b64u_encode(blob) +end + +local function decrypt_with_password(password, blob) + local bin = b64u_decode(blob) + if #bin < 1+1+6+16+12+16 then return nil, "ciphertext too short" end + local pos = 1 + local ver = bin:sub(pos,pos); pos=pos+1 + if ver ~= VERSION then return nil, "version mismatch" end + local kdfid = bin:sub(pos,pos); pos=pos+1 + if kdfid ~= KDF_ID then return nil, "kdf mismatch" end + local params = bin:sub(pos,pos+5); pos=pos+6 + local iters, dklen = unpack_params(params); if dklen ~= 32 then return nil, "bad dkLen" end + local salt = bin:sub(pos,pos+15); pos=pos+16 + + -- read the same 12-byte nonce and split the same way + local nonce12 = bin:sub(pos,pos+11); pos=pos+12 + local iv, constant = split_nonce12(nonce12) + + local tag_len = 16 + local tag = bin:sub(#bin - tag_len + 1) + local ciphertext = bin:sub(pos, #bin - tag_len) + + local key = pbkdf2_sha256(password, salt, iters, 32) + + local pt, err = aead.decrypt(AAD, key, iv, constant, ciphertext, tag) + if not pt then return nil, err or "auth failed" end + return pt +end + +return { + decrypt_with_password = decrypt_with_password, + encrypt_with_password = encrypt_with_password, + seed_entropy = seed_entropy +} diff --git a/src/lib/plc/aead_chacha_poly.lua b/src/lib/plc/aead_chacha_poly.lua new file mode 100644 index 0000000..4f12e44 --- /dev/null +++ b/src/lib/plc/aead_chacha_poly.lua @@ -0,0 +1,97 @@ +-- Copyright (c) 2015 Phil Leblanc -- see LICENSE file +------------------------------------------------------------ +--[[ + +aead_chacha_poly + +Authenticated Encryption with Associated Data (AEAD) [1], based +on Chacha20 stream encryption and Poly1305 MAC, as defined +in RFC 7539 [2]. + +[1] https://en.wikipedia.org/wiki/Authenticated_encryption +[2] http://www.rfc-editor.org/rfc/rfc7539.txt + +This file uses chacha20.lua and poly1305 for the encryption +and MAC primitives. + +]] + +local chacha20 = require "lib.plc.chacha20" +local poly1305 = require "lib.plc.poly1305" + +------------------------------------------------------------ +-- poly1305 key generation + +local poly_keygen = function(key, nonce) + local counter = 0 + local m = string.rep('\0', 64) + local e = chacha20.encrypt(key, counter, nonce, m) + -- keep only first the 256 bits (32 bytes) + return e:sub(1, 32) +end + +local pad16 = function(s) + -- return null bytes to add to s so that #s is a multiple of 16 + return (#s % 16 == 0) and "" or ('\0'):rep(16 - (#s % 16)) +end + +local app = table.insert + +local encrypt = function(aad, key, iv, constant, plain) + -- aad: additional authenticated data - arbitrary length + -- key: 32-byte string + -- iv, constant: concatenated to form the nonce (12 bytes) + -- (why not one 12-byte param? --maybe because IPsec uses + -- an 8-byte nonce) + -- implementation: RFC 7539 sect 2.8.1 + -- (memory inefficient - encr text is copied in mac_data) + local mt = {} -- mac_data table + local nonce = constant .. iv + local otk = poly_keygen(key, nonce) + local encr = chacha20.encrypt(key, 1, nonce, plain) + app(mt, aad) + app(mt, pad16(aad)) + app(mt, encr) + app(mt, pad16(encr)) + -- aad and encrypted text length must be encoded as + -- little endian _u64_ (and not u32) -- see errata at + -- https://www.rfc-editor.org/errata_search.php?rfc=7539 + app(mt, string.pack(' what could be factored?) + local mt = {} -- mac_data table + local nonce = constant .. iv + local otk = poly_keygen(key, nonce) + app(mt, aad) + app(mt, pad16(aad)) + app(mt, encr) + app(mt, pad16(encr)) + app(mt, string.pack('= 64) + local ba = table.pack(string.unpack(pat16, pt, ptidx)) + local keystream = chacha20_block(key, counter, nonce) + for i = 1, 16 do + ba[i] = bit32.bxor(ba[i], keystream[i]) + end + local es = string.pack(pat16, table.unpack(ba)) + if rbn < 64 then + es = string.sub(es, 1, rbn) + end + return es +end --chacha20_encrypt_block + +local chacha20_encrypt = function(key, counter, nonce, pt) + -- encrypt plain text 'pt', return encrypted text + -- key: 32 bytes as a string + -- counter: an uint32 (must be incremented for each block) + -- nonce: 8 bytes as a string + -- pt: plain text string, + + -- ensure counter can fit an uint32 --although it's unlikely + -- that we hit this wall with pure Lua encryption :-) + assert((counter + math.floor(#pt / 64) + 1) < 0xffffffff, + "block counter must fit an uint32") + + assert(#key == 32, "#key must be 32") + assert(#nonce == 12, "#nonce must be 12") + local keya = table.pack(string.unpack("= 16 do -- 16 = poly1305_block_size + -- h += m[i] (in rfc: a += n with 0x01 byte) + h0 = h0 + bit32.band(sunp('= 16 then + poly_blocks(st, m) + end + --handle remaining bytes + if st.bytes == 0 then -- no bytes left + -- nothing to do? no add 0x01? - apparently not. + else + local buffer = string.sub(m, st.midx) + .. '\x01' .. string.rep('\0', 16 - st.bytes -1) + assert(#buffer == 16) + st.final = true -- this is the last block +--~ p16(buffer) + poly_blocks(st, buffer) + end + -- + return st +end --poly_update + +local function poly_finish(st) + -- + local c, mask --u32 + local f --u64 + -- fully carry h + local h0 = st.h[1] + local h1 = st.h[2] + local h2 = st.h[3] + local h3 = st.h[4] + local h4 = st.h[5] + -- + c = bit32.rshift(h1,26); h1 = bit32.band(h1, 0x3ffffff) + h2 = h2 + c; c = bit32.rshift(h2, 26); h2 = bit32.band(h2, 0x3ffffff) + h3 = h3 + c; c = bit32.rshift(h3, 26); h3 = bit32.band(h3, 0x3ffffff) + h4 = h4 + c; c = bit32.rshift(h4, 26); h4 = bit32.band(h4, 0x3ffffff) + h0 = h0 + (c*5); c = bit32.rshift(h0, 26); h0 = bit32.band(h0, 0x3ffffff) + h1 = h1 + c + -- + --compute h + -p + local g0 = (h0 + 5) ; c = bit32.rshift(g0, 26); g0 = bit32.band(g0, 0x3ffffff) + local g1 = (h1 + c) ; c = bit32.rshift(g1, 26); g1 = bit32.band(g1, 0x3ffffff) + local g2 = (h2 + c) ; c = bit32.rshift(g2, 26); g2 = bit32.band(g2, 0x3ffffff) + local g3 = (h3 + c) ; c = bit32.rshift(g3, 26); g3 = bit32.band(g3, 0x3ffffff) + local g4 = bit32.band(h4 + c - 0x4000000, 0xffffffff) -- (1 << 26) + -- + -- select h if h < p, or h + -p if h >= p + mask = bit32.band(bit32.rshift(g4, 31) - 1, 0xffffffff) + -- + g0 = bit32.band(g0, mask) + g1 = bit32.band(g1, mask) + g2 = bit32.band(g2, mask) + g3 = bit32.band(g3, mask) + g4 = bit32.band(g4, mask) + -- + mask = bit32.band(bit32.bnot(mask), 0xffffffff) + h0 = bit32.bor(bit32.band(h0, mask), g0) + h1 = bit32.bor(bit32.band(h1, mask), g1) + h2 = bit32.bor(bit32.band(h2, mask), g2) + h3 = bit32.bor(bit32.band(h3, mask), g3) + h4 = bit32.bor(bit32.band(h4, mask), g4) + -- + --h = h % (2^128) + h0 = bit32.band(bit32.bor((h0), bit32.lshift(h1, 26)), 0xffffffff) + h1 = bit32.band(bit32.bor(bit32.rshift(h1, 6), bit32.lshift(h2, 20)), 0xffffffff) + h2 = bit32.band(bit32.bor(bit32.rshift(h2, 12), bit32.lshift(h3, 14)), 0xffffffff) + h3 = bit32.band(bit32.bor(bit32.rshift(h3, 18), bit32.lshift(h4, 8)), 0xffffffff) + -- + -- mac = (h + pad) % (2^128) + f = h0 + st.pad[1] ; h0 = bit32.band(f, 0xffffffff) + f = h1 + st.pad[2] + bit32.rshift(f, 32) ; h1 = bit32.band(f, 0xffffffff) + f = h2 + st.pad[3] + bit32.rshift(f, 32) ; h2 = bit32.band(f, 0xffffffff) + f = h3 + st.pad[4] + bit32.rshift(f, 32) ; h3 = bit32.band(f, 0xffffffff) + -- + local mac = string.pack(' +--- ---@class Config ---@field inventories string[] ---@field import string[]|nil ---@field chatbox table|nil +---@field remote Remote local inv = require("modules.inv") local ui = require("modules.ui") local chatbox = require("modules.chatbox") +local ra = require("modules.ra") local config = require("../config") ---@type Config @@ -39,4 +52,4 @@ local function importMechanism() end inv.sync() -parallel.waitForAll(chatbox.run, inv.getAIL().run, importMechanism, ui.runUi, inv.detectPlayerInsert) +parallel.waitForAll(inv.getAIL().run, chatbox.run, ra.run, ui.run, importMechanism, inv.detectPlayerInsert) diff --git a/src/modules/ra.lua b/src/modules/ra.lua new file mode 100644 index 0000000..9847ce3 --- /dev/null +++ b/src/modules/ra.lua @@ -0,0 +1,83 @@ +local inv = require("modules.inv"); +local config = require("../../config") ---@type Config +local hmac = require("lib.hmac-pbkdf2-aead") +---@class RemoteAccess +---@field type string +---@field data RemoteAccessData + +---@class RemoteAccessData +---@field itemName string|nil +---@field amount number|nil +---@field slots number[]|nil + +local function run() + if config.remote == nil then return end + + local modem = peripheral.wrap(config.remote.modem) + if not modem then return end + + for _, remote in pairs(config.remote.remotes) do + if not modem.isOpen(remote.port) then + modem.open(remote.port) + end + end + + ---@type ccTweaked.peripheral.Inventory + local ender_storage = peripheral.wrap(config.remote.ender_storage) + + while true do + local _, _, channel, _, message, _ = os.pullEvent("modem_message") + + for color, remote in pairs(config.remote.remotes) do + if remote.port == channel then + + local err, data = pcall(function () + return hmac.decrypt_with_password(remote.password, message) + end) + + if not data then return end + + if err == true then + + ---@type RemoteAccess + local json, errj = textutils.unserialiseJSON(data) + if json or errj ~= nil then + local parts = {} + for part in string.gmatch(color, "[^/]+") do + table.insert(parts, part) + end + if #parts ~= 3 then error("Invalid frequency string (need 3 colors)") end + ender_storage.setFrequency( + colors[parts[1]], + colors[parts[2]], + colors[parts[3]] + ) + if json.type == "withdraw" then + inv.sendItemToSelf(json.data.itemName, config.remote.ender_storage, json.data.amount) + elseif json.type == "deposit" then + if json.data.itemName then + local move = {} + for s, i in pairs(ender_storage.list()) do + if i.name == json.data.itemName then + table.insert(move, s) + end + end + inv.sendItemAwayMultiple(move, ender_storage, config.remote.ender_storage, json.data.amount) + elseif json.data.slots then + if type(json.data.slots) ~= 'table' then + return + end + + inv.sendItemAwayMultiple(json.data.slots, ender_storage, config.remote.ender_storage, json.data.amount) + end + end + end + end + end + end + end +end + +return { + run = run +} diff --git a/tiny_ra_library.lua b/tiny_ra_library.lua new file mode 100644 index 0000000..99121fd --- /dev/null +++ b/tiny_ra_library.lua @@ -0,0 +1,97 @@ +local function crypto() + local function a()local b,c=table.insert,table.concat;local function d(e,f,g,h,i)local j,k,l,m=e[f],e[g],e[h],e[i]local n;j=(j+k)%0x100000000;n=bit32.bxor(m,j)m=bit32.band(bit32.bor(bit32.lshift(n,16),bit32.rshift(n,16)),0xffffffff)l=(l+m)%0x100000000;n=bit32.bxor(k,l)k=bit32.band(bit32.bor(bit32.lshift(n,12),bit32.rshift(n,20)),0xffffffff)j=(j+k)%0x100000000;n=bit32.bxor(m,j)m=bit32.band(bit32.bor(bit32.lshift(n,8),bit32.rshift(n,24)),0xffffffff)l=(l+m)%0x100000000;n=bit32.bxor(k,l)k=bit32.band(bit32.bor(bit32.lshift(n,7),bit32.rshift(n,25)),0xffffffff)e[f],e[g],e[h],e[i]=j,k,l,m;return e end;local o={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}local p={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}local q=function(r,s,t)local e=o;local u=p;e[1],e[2],e[3],e[4]=0x61707865,0x3320646e,0x79622d32,0x6b206574;for v=1,8 do e[v+4]=r[v]end;e[13]=s;for v=1,3 do e[v+13]=t[v]end;for v=1,16 do u[v]=e[v]end;for w=1,10 do d(u,1,5,9,13)d(u,2,6,10,14)d(u,3,7,11,15)d(u,4,8,12,16)d(u,1,6,11,16)d(u,2,7,12,13)d(u,3,8,9,14)d(u,4,5,10,15)end;for v=1,16 do e[v]=bit32.band(e[v]+u[v],0xffffffff)end;return e end;local x="=64)local D=table.pack(string.unpack(x,z,A))local E=q(r,s,t)for v=1,16 do D[v]=bit32.bxor(D[v],E[v])end;local F=string.pack(x,table.unpack(D))if B<64 then F=string.sub(F,1,B)end;return F end;local G=function(r,s,t,z)assert(s+math.floor(#z/64)+1<0xffffffff,"block counter must fit an uint32")assert(#r==32,"#key must be 32")assert(#t==12,"#nonce must be 12")local H=table.pack(string.unpack("=16 do a6=a6+bit32.band(Q('=16 then T(e,U)end;if e.bytes==0 then else local ah=string.sub(U,e.midx)..'\x01'..string.rep('\0',16-e.bytes-1)assert(#ah==16)e.final=true;T(e,ah)end;return e end;local function ai(e)local l,aj;local ak;local a6=e.h[1]local a7=e.h[2]local a8=e.h[3]local a9=e.h[4]local aa=e.h[5]l=bit32.rshift(a7,26)a7=bit32.band(a7,0x3ffffff)a8=a8+l;l=bit32.rshift(a8,26)a8=bit32.band(a8,0x3ffffff)a9=a9+l;l=bit32.rshift(a9,26)a9=bit32.band(a9,0x3ffffff)aa=aa+l;l=bit32.rshift(aa,26)aa=bit32.band(aa,0x3ffffff)a6=a6+l*5;l=bit32.rshift(a6,26)a6=bit32.band(a6,0x3ffffff)a7=a7+l;local al=a6+5;l=bit32.rshift(al,26)al=bit32.band(al,0x3ffffff)local am=a7+l;l=bit32.rshift(am,26)am=bit32.band(am,0x3ffffff)local an=a8+l;l=bit32.rshift(an,26)an=bit32.band(an,0x3ffffff)local ao=a9+l;l=bit32.rshift(ao,26)ao=bit32.band(ao,0x3ffffff)local ap=bit32.band(aa+l-0x4000000,0xffffffff)aj=bit32.band(bit32.rshift(ap,31)-1,0xffffffff)al=bit32.band(al,aj)am=bit32.band(am,aj)an=bit32.band(an,aj)ao=bit32.band(ao,aj)ap=bit32.band(ap,aj)aj=bit32.band(bit32.bnot(aj),0xffffffff)a6=bit32.bor(bit32.band(a6,aj),al)a7=bit32.bor(bit32.band(a7,aj),am)a8=bit32.bor(bit32.band(a8,aj),an)a9=bit32.bor(bit32.band(a9,aj),ao)aa=bit32.bor(bit32.band(aa,aj),ap)a6=bit32.band(bit32.bor(a6,bit32.lshift(a7,26)),0xffffffff)a7=bit32.band(bit32.bor(bit32.rshift(a7,6),bit32.lshift(a8,20)),0xffffffff)a8=bit32.band(bit32.bor(bit32.rshift(a8,12),bit32.lshift(a9,14)),0xffffffff)a9=bit32.band(bit32.bor(bit32.rshift(a9,18),bit32.lshift(aa,8)),0xffffffff)ak=a6+e.pad[1]a6=bit32.band(ak,0xffffffff)ak=a7+e.pad[2]+bit32.rshift(ak,32)a7=bit32.band(ak,0xffffffff)ak=a8+e.pad[3]+bit32.rshift(ak,32)a8=bit32.band(ak,0xffffffff)ak=a9+e.pad[4]+bit32.rshift(ak,32)a9=bit32.band(ak,0xffffffff)local aq=string.pack('b0 then r=sha256(r):gsub('..',function(b1)return string.char(tonumber(b1,16))end)end;if#r0 then bl.K=aZ(bl.K,bl.V.."\1"..bn)bl.V=aZ(bl.K,bl.V)end end;local function bo(bp)bl.K=string.rep("\0",32)bl.V=string.rep("\1",32)bm(bp)bl.inited=true end;local function bq(aT)if not bl.inited then error("DRBG not seeded. Call seed_entropy() once.")end;local aU={}while#table.concat(aU)0 then bu[#bu+1]=bt end;local bp=sha256(table.concat(bu)):gsub('..',function(b1)return string.char(tonumber(b1,16))end)bo(bp)local ak=fs.open(bk,"wb")ak.write(bq(48))ak.close()end end;local bx="\1"local by="\1"local bz="SiSS RA rev1|chacha20poly1305"local function bA(aT)return string.char(bit32.band(bit32.rshift(aT,24),255),bit32.band(bit32.rshift(aT,16),255),bit32.band(bit32.rshift(aT,8),255),bit32.band(aT,255))end;local function bB(aT)return string.char(bit32.band(bit32.rshift(aT,8),255),bit32.band(aT,255))end;local function bC(bD)return bA(bD)..bB(32)end;local function bE(aA)local bF,bG,bH,bI,ac,ad=aA:byte(1,6)local bJ=bit32.bor(bit32.lshift(bF,24),bit32.lshift(bG,16),bit32.lshift(bH,8),bI)local bK=bit32.bor(bit32.lshift(ac,8),ad)return bJ,bK end;local function bL(aT)return bq(aT)end;local function bM(O)assert(#O==12,"nonce must be 12 bytes")local aD=O:sub(1,6)local aE=O:sub(7,12)return aD,aE end;local function bN(b7,bO)local b8=bL(16)local bJ=1000;local r=b6(b7,b8,bJ,32)local O=bL(12)local aD,aE=bM(O)local bP,aK=aN.encrypt(bz,r,aD,aE,bO)local bQ=bC(bJ)local bR=table.concat{bx,by,bQ,b8,O,bP,aK}return aR(bR)end;local function bS(b7,bR)local aS=aW(bR)if#aS<1+1+6+16+12+16 then return nil,"ciphertext too short"end;local bT=1;local bU=aS:sub(bT,bT)bT=bT+1;if bU~=bx then return nil,"version mismatch"end;local bV=aS:sub(bT,bT)bT=bT+1;if bV~=by then return nil,"kdf mismatch"end;local bQ=aS:sub(bT,bT+5)bT=bT+6;local bJ,bK=bE(bQ)if bK~=32 then return nil,"bad dkLen"end;local b8=aS:sub(bT,bT+15)bT=bT+16;local O=aS:sub(bT,bT+11)bT=bT+12;local aD,aE=bM(O)local bW=16;local aK=aS:sub(#aS-bW+1)local bP=aS:sub(bT,#aS-bW)local r=b6(b7,b8,bJ,32)local z,bX=aN.decrypt(bz,r,aD,aE,bP,aK)if not z then return nil,bX or"auth failed"end;return z end;return{decrypt_with_password=bS,encrypt_with_password=bN,seed_entropy=bs} +end +local hmac = crypto() + + +---@type string +local password +---@type number +local port + +---@type ccTweaked.peripheral.Modem +local modem + +local function init(modemI, portI, passwordI) + port = portI + password = passwordI + modem = modemI + + hmac.seed_entropy() + modem.open(port) + +end + +---@param itemName string +---@param count number|nil +local function withdraw(itemName, count) + if not port or not modem or not password then + error("tiny_ra_library: init was never ran") + end + + local data = { + itemName = itemName + } + + if count then + data["count"] = count + end + + modem.transmit(port, port, hmac.encrypt_with_password(password, + textutils.serialiseJSON({ + ["type"] = "withdraw", + ["data"] = data + }))) +end + +---@param slots number[] +---@param count number|nil +local function depositBySlots(slots, count) + if not port or not modem or not password then + error("tiny_ra_library: init was never ran") + end + + local data = { + slots = slots + } + + if count then + data["count"] = count + end + + modem.transmit(port, port, hmac.encrypt_with_password(password, + textutils.serialiseJSON({ + ["type"] = "deposit", + ["data"] = data + }))) +end + +---@param itemName string +---@param count number|nil +local function depositByItemName(itemName, count) + if not port or not modem or not password then + error("tiny_ra_library: init was never ran") + end + + local data = { + itemName = itemName + } + + if count then + data["count"] = count + end + + modem.transmit(port, port, hmac.encrypt_with_password(password, + textutils.serialiseJSON({ + ["type"] = "deposit", + ["data"] = data + }))) +end + +return { + init = init, + + withdraw = withdraw, + depositByItemName = depositByItemName, + depositBySlots = depositBySlots +}