Reformat + add prefix selection for config

This commit is contained in:
Soph :3 2025-11-14 22:43:49 +02:00
parent 88f4173c8e
commit 262c0b5408
13 changed files with 1191 additions and 1141 deletions

View file

@ -1,81 +1,94 @@
local g = string.gsub
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]<q then r[2]=r[2]+1;r[1]=q-(e-1-r[1])-1 else r[1]=r[1]+qVUrVSYs(t)Su=#t;t[#t+1]=0x80;while#t%64~=56Zt[#t+1]=0VSv=q({0,0},u*8)fWw=2,1,-1Zt[#t+1]=a(k(a(v[w]TFF000000),24)TFF)t[#t+1]=a(k(a(v[w]TFF0000),16)TFF)t[#t+1]=a(k(a(v[w]TFF00),8)TFF)t[#t+1]=a(v[w]TFF)VUtVSYx(y,w)Uc(y[w]W0,24)+c(y[w+1]W0,16)+c(y[w+2]W0,8)+(y[w+3]W0)VSYz(t,w,A)SB={}fWC=1,16ZB[C]=x(t,w+(C-1)*4)VfWC=17,64ZSD=B[C-15]SE=b(b(f(B[C-15],7),f(B[C-15],18)),k(B[C-15],3))SF=b(b(f(B[C-2],17),f(B[C-2],19)),k(B[C-2],10))B[C]=(B[C-16]+E+B[C-7]+F)%eVSG,h,H,I,J,j,K,L=d(A)fWC=1,64ZSM=b(b(f(J,6),f(J,11)),f(J,25))SN=b(a(J,j),a(Xbnot(J),K))SO=(L+M+N+p[C]+B[C])%eSP=b(b(f(G,2),f(G,13)),f(G,22))SQ=b(b(a(G,h),a(G,H)),a(h,H))SR=(P+Q)%e;L,K,j,J,I,H,h,G=K,j,J,(I+O)%e,H,h,G,(O+R)%eVA[1]=(A[1]+G)%e;A[2]=(A[2]+h)%e;A[3]=(A[3]+H)%e;A[4]=(A[4]+I)%e;A[5]=(A[5]+J)%e;A[6]=(A[6]+j)%e;A[7]=(A[7]+K)%e;A[8]=(A[8]+L)%eUAVUY(t)t=t W""t=type(t)=="string"and{t:byte(1,-1)}Wt;t=s(t)SA={d(o)}fWw=1,#t,64ZA=z(t,w,A)VU("%08x"):rep(8):format(d(A))V',"S"," local "),"T",",0x"),"U"," return "),"V"," end "),"W","or "),"X","bit32."),"Y","function "),"Z"," do "))()
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]<q then r[2]=r[2]+1;r[1]=q-(e-1-r[1])-1 else r[1]=r[1]+qVUrVSYs(t)Su=#t;t[#t+1]=0x80;while#t%64~=56Zt[#t+1]=0VSv=q({0,0},u*8)fWw=2,1,-1Zt[#t+1]=a(k(a(v[w]TFF000000),24)TFF)t[#t+1]=a(k(a(v[w]TFF0000),16)TFF)t[#t+1]=a(k(a(v[w]TFF00),8)TFF)t[#t+1]=a(v[w]TFF)VUtVSYx(y,w)Uc(y[w]W0,24)+c(y[w+1]W0,16)+c(y[w+2]W0,8)+(y[w+3]W0)VSYz(t,w,A)SB={}fWC=1,16ZB[C]=x(t,w+(C-1)*4)VfWC=17,64ZSD=B[C-15]SE=b(b(f(B[C-15],7),f(B[C-15],18)),k(B[C-15],3))SF=b(b(f(B[C-2],17),f(B[C-2],19)),k(B[C-2],10))B[C]=(B[C-16]+E+B[C-7]+F)%eVSG,h,H,I,J,j,K,L=d(A)fWC=1,64ZSM=b(b(f(J,6),f(J,11)),f(J,25))SN=b(a(J,j),a(Xbnot(J),K))SO=(L+M+N+p[C]+B[C])%eSP=b(b(f(G,2),f(G,13)),f(G,22))SQ=b(b(a(G,h),a(G,H)),a(h,H))SR=(P+Q)%e;L,K,j,J,I,H,h,G=K,j,J,(I+O)%e,H,h,G,(O+R)%eVA[1]=(A[1]+G)%e;A[2]=(A[2]+h)%e;A[3]=(A[3]+H)%e;A[4]=(A[4]+I)%e;A[5]=(A[5]+J)%e;A[6]=(A[6]+j)%e;A[7]=(A[7]+K)%e;A[8]=(A[8]+L)%eUAVUY(t)t=t W""t=type(t)=="string"and{t:byte(1,-1)}Wt;t=s(t)SA={d(o)}fWw=1,#t,64ZA=z(t,w,A)VU("%08x"):rep(8):format(d(A))V',
"S", " local "), "T", ",0x"), "U", " return "), "V", " end "), "W", "or "), "X", "bit32."), "Y", "function "), "Z",
" do "))()
local aead = require("lib.plc.aead_chacha_poly")
local aead = require("lib.plc.aead_chacha_poly")
local function tobytes(s) return {s:byte(1, #s)} end
local function tobytes(s) return { s:byte(1, #s) } end
local function frombytes(t) return string.char(table.unpack(t)) end
local b64abc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
local function b64u_encode(bin)
local t, n, out = tobytes(bin), #bin, {}
local i=1
while i<=n do
local a=t[i] or 0; local b=t[i+1] or 0; local c=t[i+2] or 0
local triple = a*65536 + b*256 + c
out[#out+1]=b64abc:sub(bit32.band(bit32.rshift(triple,18), 63)+1,bit32.band(bit32.rshift(triple,18), 63)+1)
out[#out+1]=b64abc:sub(bit32.band(bit32.rshift(triple,12), 63)+1,bit32.band(bit32.rshift(triple,12), 63)+1)
out[#out+1]= i+1<=n and b64abc:sub(bit32.band(bit32.rshift(triple,6), 63)+1,bit32.band(bit32.rshift(triple,6), 63)+1) or ''
out[#out+1]= i+2<=n and b64abc:sub(bit32.band(bit32.rshift(triple,0), 63)+1,bit32.band(bit32.rshift(triple,0), 63)+1) or ''
i=i+3
local i = 1
while i <= n do
local a = t[i] or 0; local b = t[i + 1] or 0; local c = t[i + 2] or 0
local triple = a * 65536 + b * 256 + c
out[#out + 1] = b64abc:sub(bit32.band(bit32.rshift(triple, 18), 63) + 1, bit32.band(bit32.rshift(triple, 18), 63) + 1)
out[#out + 1] = b64abc:sub(bit32.band(bit32.rshift(triple, 12), 63) + 1, bit32.band(bit32.rshift(triple, 12), 63) + 1)
out[#out + 1] = i + 1 <= n and
b64abc:sub(bit32.band(bit32.rshift(triple, 6), 63) + 1, bit32.band(bit32.rshift(triple, 6), 63) + 1) or ''
out[#out + 1] = i + 2 <= n and
b64abc:sub(bit32.band(bit32.rshift(triple, 0), 63) + 1, bit32.band(bit32.rshift(triple, 0), 63) + 1) or ''
i = i + 3
end
return table.concat(out)
end
local function b64u_decode(txt)
local map = {}
for i=1,#b64abc do map[b64abc:sub(i,i)] = i-1 end
for i = 1, #b64abc do map[b64abc:sub(i, i)] = i - 1 end
local out = {}
local i=1
while i<=#txt do
local a = map[txt:sub(i,i)]; i=i+1
local b = map[txt:sub(i,i)]; i=i+1
local c = map[txt:sub(i,i)]; i=i+1
local d = map[txt:sub(i,i)]; i=i+1
if a==nil or b==nil then break end
local triple = bit32.bor(bit32.lshift(a,18) , bit32.lshift(b,12) , bit32.lshift(c or 0,6) , (d or 0))
out[#out+1] = string.char(bit32.band(bit32.rshift(triple,16) , 255))
if c ~= nil then out[#out+1] = string.char(bit32.band(bit32.rshift(triple,8) , 255)) end
if d ~= nil then out[#out+1] = string.char(bit32.band(triple , 255)) end
local i = 1
while i <= #txt do
local a = map[txt:sub(i, i)]; i = i + 1
local b = map[txt:sub(i, i)]; i = i + 1
local c = map[txt:sub(i, i)]; i = i + 1
local d = map[txt:sub(i, i)]; i = i + 1
if a == nil or b == nil then break end
local triple = bit32.bor(bit32.lshift(a, 18), bit32.lshift(b, 12), bit32.lshift(c or 0, 6), (d or 0))
out[#out + 1] = string.char(bit32.band(bit32.rshift(triple, 16), 255))
if c ~= nil then out[#out + 1] = string.char(bit32.band(bit32.rshift(triple, 8), 255)) end
if d ~= nil then out[#out + 1] = string.char(bit32.band(triple, 255)) end
end
return table.concat(out)
end
local function hmac_sha256(key, msg)
local block = 64
if #key > block then key = (sha256(key):gsub('..', function(h) return string.char(tonumber(h,16)) end)) end
if #key > 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 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)
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)
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))
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 r = dkLen - (l - 1) * hLen
local dk = {}
for i=1,l do
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
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
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 i<l then
dk[#dk+1] = string.char(table.unpack(T))
if i < l then
dk[#dk + 1] = string.char(table.unpack(T))
else
dk[#dk+1] = string.char(table.unpack(T, 1, r))
dk[#dk + 1] = string.char(table.unpack(T, 1, r))
end
end
return table.concat(dk)
@ -83,20 +96,20 @@ end
-- HMAC-DRBG
local SEED_PATH = "siss_drg_seed.bin"
local DRBG = {K = string.rep("\0",32), V = string.rep("\1",32), inited=false}
local DRBG = { K = string.rep("\0", 32), V = string.rep("\1", 32), inited = false }
local function drbg_update(provided_data)
DRBG.K = hmac_sha256(DRBG.K, DRBG.V .. "\0" .. (provided_data or ""))
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
if provided_data and #provided_data>0 then
if provided_data and #provided_data > 0 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.K = string.rep("\0", 32)
DRBG.V = string.rep("\1", 32)
drbg_update(seed)
DRBG.inited = true
end
@ -106,27 +119,27 @@ local function drbg_bytes(n)
local out = {}
while #table.concat(out) < n do
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
out[#out+1] = DRBG.V
out[#out + 1] = DRBG.V
end
drbg_update("")
local buf = table.concat(out)
return buf:sub(1,n)
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)
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
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)
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)
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))
@ -138,15 +151,16 @@ 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 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))
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
@ -170,24 +184,24 @@ local function encrypt_with_password(password, plaintext)
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 }
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
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
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
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 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
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 nonce12 = bin:sub(pos, pos + 11); pos = pos + 12
local iv, constant = split_nonce12(nonce12)
local tag_len = 16

View file

@ -23,67 +23,67 @@ 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)
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))
-- 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('<I8', #aad))
app(mt, string.pack('<I8', #encr))
local mac_data = table.concat(mt)
--~ p16('mac', mac_data)
local tag = poly1305.auth(mac_data, otk)
return encr, tag
-- 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('<I8', #aad))
app(mt, string.pack('<I8', #encr))
local mac_data = table.concat(mt)
--~ p16('mac', mac_data)
local tag = poly1305.auth(mac_data, otk)
return encr, tag
end --chacha20_aead_encrypt()
local function decrypt(aad, key, iv, constant, encr, tag)
-- (memory inefficient - encr text is copied in mac_data)
-- (structure similar to aead_encrypt => 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('<I8', #aad))
app(mt, string.pack('<I8', #encr))
local mac_data = table.concat(mt)
local mac = poly1305.auth(mac_data, otk)
if mac == tag then
local plain = chacha20.encrypt(key, 1, nonce, encr)
return plain
else
return nil, "auth failed"
end
-- (memory inefficient - encr text is copied in mac_data)
-- (structure similar to aead_encrypt => 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('<I8', #aad))
app(mt, string.pack('<I8', #encr))
local mac_data = table.concat(mt)
local mac = poly1305.auth(mac_data, otk)
if mac == tag then
local plain = chacha20.encrypt(key, 1, nonce, encr)
return plain
else
return nil, "auth failed"
end
end --chacha20_aead_decrypt()
@ -91,7 +91,7 @@ end --chacha20_aead_decrypt()
-- return aead_chacha_poly module
return {
poly_keygen = poly_keygen,
encrypt = encrypt,
decrypt = decrypt,
}
poly_keygen = poly_keygen,
encrypt = encrypt,
decrypt = decrypt,
}

View file

@ -27,178 +27,178 @@ local app, concat = table.insert, table.concat
local function qround(st, x, y, z, w)
-- st is a chacha state: an array of 16 u32 words
-- x,y,z,w are indices in st
local a, b, c, d = st[x], st[y], st[z], st[w]
local t
-- st is a chacha state: an array of 16 u32 words
-- x,y,z,w are indices in st
local a, b, c, d = st[x], st[y], st[z], st[w]
local t
a = (a + b) % 0x100000000
t = bit32.bxor(d, a)
d = bit32.band(bit32.bor(bit32.lshift(t, 16), bit32.rshift(t, 16)), 0xffffffff)
a = (a + b) % 0x100000000
t = bit32.bxor(d, a)
d = bit32.band(bit32.bor(bit32.lshift(t, 16), bit32.rshift(t, 16)), 0xffffffff)
c = (c + d) % 0x100000000
t = bit32.bxor(b, c)
b = bit32.band(bit32.bor(bit32.lshift(t, 12), bit32.rshift(t, 20)), 0xffffffff)
c = (c + d) % 0x100000000
t = bit32.bxor(b, c)
b = bit32.band(bit32.bor(bit32.lshift(t, 12), bit32.rshift(t, 20)), 0xffffffff)
a = (a + b) % 0x100000000
t = bit32.bxor(d, a)
d = bit32.band(bit32.bor(bit32.lshift(t, 8), bit32.rshift(t, 24)), 0xffffffff)
a = (a + b) % 0x100000000
t = bit32.bxor(d, a)
d = bit32.band(bit32.bor(bit32.lshift(t, 8), bit32.rshift(t, 24)), 0xffffffff)
c = (c + d) % 0x100000000
t = bit32.bxor(b, c)
b = bit32.band(bit32.bor(bit32.lshift(t, 7), bit32.rshift(t, 25)), 0xffffffff)
c = (c + d) % 0x100000000
t = bit32.bxor(b, c)
b = bit32.band(bit32.bor(bit32.lshift(t, 7), bit32.rshift(t, 25)), 0xffffffff)
st[x], st[y], st[z], st[w] = a, b, c, d
return st
st[x], st[y], st[z], st[w] = a, b, c, d
return st
end
-- chacha20 state and working state are allocated once and reused
-- by each invocation of chacha20_block()
local chacha20_state = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
local chacha20_working_state = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
local chacha20_state = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
local chacha20_working_state = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
local chacha20_block = function(key, counter, nonce)
-- key: u32[8]
-- counter: u32
-- nonce: u32[3]
local st = chacha20_state -- state
local wst = chacha20_working_state -- working state
-- initialize state
st[1], st[2], st[3], st[4] =
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
for i = 1, 8 do st[i+4] = key[i] end
st[13] = counter
for i = 1, 3 do st[i+13] = nonce[i] end
-- copy state to working_state
for i = 1, 16 do wst[i] = st[i] end
-- run 20 rounds, ie. 10 iterations of 8 quarter rounds
for _ = 1, 10 do --RFC reference:
qround(wst, 1,5,9,13) --1. QUARTERROUND ( 0, 4, 8,12)
qround(wst, 2,6,10,14) --2. QUARTERROUND ( 1, 5, 9,13)
qround(wst, 3,7,11,15) --3. QUARTERROUND ( 2, 6,10,14)
qround(wst, 4,8,12,16) --4. QUARTERROUND ( 3, 7,11,15)
qround(wst, 1,6,11,16) --5. QUARTERROUND ( 0, 5,10,15)
qround(wst, 2,7,12,13) --6. QUARTERROUND ( 1, 6,11,12)
qround(wst, 3,8,9,14) --7. QUARTERROUND ( 2, 7, 8,13)
qround(wst, 4,5,10,15) --8. QUARTERROUND ( 3, 4, 9,14)
end
-- add working_state to state
for i = 1, 16 do st[i] = bit32.band((st[i] + wst[i]), 0xffffffff) end
-- return st, an array of 16 u32 words used as a keystream
return st
-- key: u32[8]
-- counter: u32
-- nonce: u32[3]
local st = chacha20_state -- state
local wst = chacha20_working_state -- working state
-- initialize state
st[1], st[2], st[3], st[4] =
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
for i = 1, 8 do st[i + 4] = key[i] end
st[13] = counter
for i = 1, 3 do st[i + 13] = nonce[i] end
-- copy state to working_state
for i = 1, 16 do wst[i] = st[i] end
-- run 20 rounds, ie. 10 iterations of 8 quarter rounds
for _ = 1, 10 do --RFC reference:
qround(wst, 1, 5, 9, 13) --1. QUARTERROUND ( 0, 4, 8,12)
qround(wst, 2, 6, 10, 14) --2. QUARTERROUND ( 1, 5, 9,13)
qround(wst, 3, 7, 11, 15) --3. QUARTERROUND ( 2, 6,10,14)
qround(wst, 4, 8, 12, 16) --4. QUARTERROUND ( 3, 7,11,15)
qround(wst, 1, 6, 11, 16) --5. QUARTERROUND ( 0, 5,10,15)
qround(wst, 2, 7, 12, 13) --6. QUARTERROUND ( 1, 6,11,12)
qround(wst, 3, 8, 9, 14) --7. QUARTERROUND ( 2, 7, 8,13)
qround(wst, 4, 5, 10, 15) --8. QUARTERROUND ( 3, 4, 9,14)
end
-- add working_state to state
for i = 1, 16 do st[i] = bit32.band((st[i] + wst[i]), 0xffffffff) end
-- return st, an array of 16 u32 words used as a keystream
return st
end --chacha20_block()
-- pat16: used to unpack a 64-byte string as 16 uint32
local pat16 = "<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4"
local function chacha20_encrypt_block(key, counter, nonce, pt, ptidx)
-- encrypt a 64-byte block of plain text.
-- key: 32 bytes as an array of 8 uint32
-- counter: an uint32 (must be incremented for each block)
-- nonce: 12 bytes as an array of 3 uint32
-- pt: plain text string,
-- ptidx: index of beginning of block in plain text (origin=1)
-- if less than 64 bytes are left at position ptidx, it is padded
-- with null bytes before encryption and result is stripped
-- accordingly.
-- return encrypted block as a string (length <= 16)
local rbn = #pt - ptidx + 1 -- number of remaining bytes in pt
if rbn < 64 then
local tmp = string.sub(pt, ptidx)
pt = tmp .. string.rep('\0', 64 - rbn) --pad last block
ptidx = 1
end
assert(#pt >= 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
-- encrypt a 64-byte block of plain text.
-- key: 32 bytes as an array of 8 uint32
-- counter: an uint32 (must be incremented for each block)
-- nonce: 12 bytes as an array of 3 uint32
-- pt: plain text string,
-- ptidx: index of beginning of block in plain text (origin=1)
-- if less than 64 bytes are left at position ptidx, it is padded
-- with null bytes before encryption and result is stripped
-- accordingly.
-- return encrypted block as a string (length <= 16)
local rbn = #pt - ptidx + 1 -- number of remaining bytes in pt
if rbn < 64 then
local tmp = string.sub(pt, ptidx)
pt = tmp .. string.rep('\0', 64 - rbn) --pad last block
ptidx = 1
end
assert(#pt >= 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,
-- 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,
-- 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("<I4I4I4I4I4I4I4I4", key))
local noncea = table.pack(string.unpack("<I4I4I4", nonce))
local t = {} -- used to collect all encrypted blocks
local ptidx = 1
while ptidx <= #pt do
app(t, chacha20_encrypt_block(keya, counter, noncea, pt, ptidx))
ptidx = ptidx + 64
counter = counter + 1
end
local et = concat(t)
return et
assert(#key == 32, "#key must be 32")
assert(#nonce == 12, "#nonce must be 12")
local keya = table.pack(string.unpack("<I4I4I4I4I4I4I4I4", key))
local noncea = table.pack(string.unpack("<I4I4I4", nonce))
local t = {} -- used to collect all encrypted blocks
local ptidx = 1
while ptidx <= #pt do
app(t, chacha20_encrypt_block(keya, counter, noncea, pt, ptidx))
ptidx = ptidx + 64
counter = counter + 1
end
local et = concat(t)
return et
end --chacha20_encrypt()
local function hchacha20(key, nonce16)
-- key: string(32)
-- nonce16: string(16)
local keya = table.pack(string.unpack("<I4I4I4I4I4I4I4I4", key))
local noncea = table.pack(string.unpack("<I4I4I4I4", nonce16))
local st = {} -- chacha working state
-- initialize state
st[1], st[2], st[3], st[4] =
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
for i = 1, 8 do st[i+4] = keya[i] end
for i = 1, 4 do st[i+12] = noncea[i] end
-- run 20 rounds, ie. 10 iterations of 8 quarter rounds
for _ = 1, 10 do --RFC reference:
qround(st, 1,5,9,13) --1. QUARTERROUND ( 0, 4, 8,12)
qround(st, 2,6,10,14) --2. QUARTERROUND ( 1, 5, 9,13)
qround(st, 3,7,11,15) --3. QUARTERROUND ( 2, 6,10,14)
qround(st, 4,8,12,16) --4. QUARTERROUND ( 3, 7,11,15)
qround(st, 1,6,11,16) --5. QUARTERROUND ( 0, 5,10,15)
qround(st, 2,7,12,13) --6. QUARTERROUND ( 1, 6,11,12)
qround(st, 3,8,9,14) --7. QUARTERROUND ( 2, 7, 8,13)
qround(st, 4,5,10,15) --8. QUARTERROUND ( 3, 4, 9,14)
end
local subkey = string.pack("<I4I4I4I4I4I4I4I4",
st[1], st[2], st[3], st[4],
st[13], st[14], st[15], st[16] )
return subkey
-- key: string(32)
-- nonce16: string(16)
local keya = table.pack(string.unpack("<I4I4I4I4I4I4I4I4", key))
local noncea = table.pack(string.unpack("<I4I4I4I4", nonce16))
local st = {} -- chacha working state
-- initialize state
st[1], st[2], st[3], st[4] =
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
for i = 1, 8 do st[i + 4] = keya[i] end
for i = 1, 4 do st[i + 12] = noncea[i] end
-- run 20 rounds, ie. 10 iterations of 8 quarter rounds
for _ = 1, 10 do --RFC reference:
qround(st, 1, 5, 9, 13) --1. QUARTERROUND ( 0, 4, 8,12)
qround(st, 2, 6, 10, 14) --2. QUARTERROUND ( 1, 5, 9,13)
qround(st, 3, 7, 11, 15) --3. QUARTERROUND ( 2, 6,10,14)
qround(st, 4, 8, 12, 16) --4. QUARTERROUND ( 3, 7,11,15)
qround(st, 1, 6, 11, 16) --5. QUARTERROUND ( 0, 5,10,15)
qround(st, 2, 7, 12, 13) --6. QUARTERROUND ( 1, 6,11,12)
qround(st, 3, 8, 9, 14) --7. QUARTERROUND ( 2, 7, 8,13)
qround(st, 4, 5, 10, 15) --8. QUARTERROUND ( 3, 4, 9,14)
end
local subkey = string.pack("<I4I4I4I4I4I4I4I4",
st[1], st[2], st[3], st[4],
st[13], st[14], st[15], st[16])
return subkey
end --hchacha20()
local function xchacha20_encrypt(key, counter, nonce, pt)
assert(#key == 32, "#key must be 32")
assert(#nonce == 24, "#nonce must be 24")
local subkey = hchacha20(key, nonce:sub(1, 16))
local nonce12 = '\0\0\0\0'..nonce:sub(17)
return chacha20_encrypt(subkey, counter, nonce12, pt)
assert(#key == 32, "#key must be 32")
assert(#nonce == 24, "#nonce must be 24")
local subkey = hchacha20(key, nonce:sub(1, 16))
local nonce12 = '\0\0\0\0' .. nonce:sub(17)
return chacha20_encrypt(subkey, counter, nonce12, pt)
end --xchacha20_encrypt()
------------------------------------------------------------
return {
chacha20_encrypt = chacha20_encrypt,
chacha20_decrypt = chacha20_encrypt, -- xor encryption is symmetric
encrypt = chacha20_encrypt, --alias
decrypt = chacha20_encrypt, --alias
hchacha20 = hchacha20,
xchacha20_encrypt = xchacha20_encrypt,
xchacha20_decrypt = xchacha20_encrypt,
--
key_size = 32,
nonce_size = 12, -- nonce size for chacha20_encrypt
xnonce_size = 24, -- nonce size for xchacha20_encrypt
}
chacha20_encrypt = chacha20_encrypt,
chacha20_decrypt = chacha20_encrypt, -- xor encryption is symmetric
encrypt = chacha20_encrypt, --alias
decrypt = chacha20_encrypt, --alias
hchacha20 = hchacha20,
xchacha20_encrypt = xchacha20_encrypt,
xchacha20_decrypt = xchacha20_encrypt,
--
key_size = 32,
nonce_size = 12, -- nonce size for chacha20_encrypt
xnonce_size = 24, -- nonce size for xchacha20_encrypt
}
--end of chacha20

View file

@ -13,192 +13,192 @@ local sunp = string.unpack
local bit32 = bit32 -- alias
local function poly_init(k)
-- k: 32-byte key as a string
-- initialize internal state
local st = {
r = {
bit32.band(sunp('<I4', k, 1) , 0x3ffffff), -- r0
bit32.band(bit32.rshift(sunp('<I4', k, 4), 2), 0x3ffff03), -- r1
bit32.band(bit32.rshift(sunp('<I4', k, 7), 4), 0x3ffc0ff), -- r2
bit32.band(bit32.rshift(sunp('<I4', k, 10), 6), 0x3f03fff), -- r3
bit32.band(bit32.rshift(sunp('<I4', k, 13), 8), 0x00fffff), -- r4
},
h = { 0,0,0,0,0 },
pad = { sunp('<I4', k, 17), -- 's' in rfc
sunp('<I4', k, 21),
sunp('<I4', k, 25),
sunp('<I4', k, 29),
},
buffer = "", --
leftover = 0,
final = false,
}--st
return st
-- k: 32-byte key as a string
-- initialize internal state
local st = {
r = {
bit32.band(sunp('<I4', k, 1), 0x3ffffff), -- r0
bit32.band(bit32.rshift(sunp('<I4', k, 4), 2), 0x3ffff03), -- r1
bit32.band(bit32.rshift(sunp('<I4', k, 7), 4), 0x3ffc0ff), -- r2
bit32.band(bit32.rshift(sunp('<I4', k, 10), 6), 0x3f03fff), -- r3
bit32.band(bit32.rshift(sunp('<I4', k, 13), 8), 0x00fffff), -- r4
},
h = { 0, 0, 0, 0, 0 },
pad = { sunp('<I4', k, 17), -- 's' in rfc
sunp('<I4', k, 21),
sunp('<I4', k, 25),
sunp('<I4', k, 29),
},
buffer = "", --
leftover = 0,
final = false,
} --st
return st
end --poly_init()
local function poly_blocks(st, m)
-- st: internal state
-- m: message:string
local bytes = #m
local midx = 1
local hibit = st.final and 0 or 0x01000000 -- 1 << 24
local r0 = st.r[1]
local r1 = st.r[2]
local r2 = st.r[3]
local r3 = st.r[4]
local r4 = st.r[5]
local s1 = r1 * 5
local s2 = r2 * 5
local s3 = r3 * 5
local s4 = r4 * 5
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]
local d0, d1, d2, d3, d4, c
--
while bytes >= 16 do -- 16 = poly1305_block_size
-- h += m[i] (in rfc: a += n with 0x01 byte)
h0 = h0 + bit32.band(sunp('<I4', m, midx ), 0x3ffffff)
h1 = h1 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 3), 2), 0x3ffffff)
h2 = h2 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 6), 4), 0x3ffffff)
h3 = h3 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 9), 6), 0x3ffffff)
h4 = h4 + bit32.bor(bit32.rshift(sunp('<I4', m, midx + 12), 8), hibit) -- 0x01 byte
--
-- h *= r % p (partial)
d0 = h0*r0 + h1*s4 + h2*s3 + h3*s2 + h4*s1
d1 = h0*r1 + h1*r0 + h2*s4 + h3*s3 + h4*s2
d2 = h0*r2 + h1*r1 + h2*r0 + h3*s4 + h4*s3
d3 = h0*r3 + h1*r2 + h2*r1 + h3*r0 + h4*s4
d4 = h0*r4 + h1*r3 + h2*r2 + h3*r1 + h4*r0
--
c = bit32.band(bit32.rshift(d0,26), 0xffffffff) ; h0 = bit32.band(d0, 0x3ffffff)
d1 = d1 + c ; c = bit32.band(bit32.rshift(d1,26), 0xffffffff) ; h1 = bit32.band(d1, 0x3ffffff)
d2 = d2 + c ; c = bit32.band(bit32.rshift(d2,26), 0xffffffff) ; h2 = bit32.band(d2, 0x3ffffff)
d3 = d3 + c ; c = bit32.band(bit32.rshift(d3,26), 0xffffffff) ; h3 = bit32.band(d3, 0x3ffffff)
d4 = d4 + c ; c = bit32.band(bit32.rshift(d4,26), 0xffffffff) ; h4 = bit32.band(d4, 0x3ffffff)
h0 = h0 + (c*5) ; c = bit32.rshift(h0,26) ; h0 = bit32.band(h0, 0x3ffffff)
h1 = h1 + c
--
midx = midx + 16 -- 16 = poly1305_block_size
bytes = bytes - 16
end --while
st.h[1] = h0
st.h[2] = h1
st.h[3] = h2
st.h[4] = h3
st.h[5] = h4
st.bytes = bytes -- remaining bytes. must be < 16 here
st.midx = midx -- index of first remaining bytes
return st
end --poly_blocks()
-- st: internal state
-- m: message:string
local bytes = #m
local midx = 1
local hibit = st.final and 0 or 0x01000000 -- 1 << 24
local r0 = st.r[1]
local r1 = st.r[2]
local r2 = st.r[3]
local r3 = st.r[4]
local r4 = st.r[5]
local s1 = r1 * 5
local s2 = r2 * 5
local s3 = r3 * 5
local s4 = r4 * 5
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]
local d0, d1, d2, d3, d4, c
--
while bytes >= 16 do -- 16 = poly1305_block_size
-- h += m[i] (in rfc: a += n with 0x01 byte)
h0 = h0 + bit32.band(sunp('<I4', m, midx), 0x3ffffff)
h1 = h1 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 3), 2), 0x3ffffff)
h2 = h2 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 6), 4), 0x3ffffff)
h3 = h3 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 9), 6), 0x3ffffff)
h4 = h4 + bit32.bor(bit32.rshift(sunp('<I4', m, midx + 12), 8), hibit) -- 0x01 byte
--
-- h *= r % p (partial)
d0 = h0 * r0 + h1 * s4 + h2 * s3 + h3 * s2 + h4 * s1
d1 = h0 * r1 + h1 * r0 + h2 * s4 + h3 * s3 + h4 * s2
d2 = h0 * r2 + h1 * r1 + h2 * r0 + h3 * s4 + h4 * s3
d3 = h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * s4
d4 = h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0
--
c = bit32.band(bit32.rshift(d0, 26), 0xffffffff); h0 = bit32.band(d0, 0x3ffffff)
d1 = d1 + c; c = bit32.band(bit32.rshift(d1, 26), 0xffffffff); h1 = bit32.band(d1, 0x3ffffff)
d2 = d2 + c; c = bit32.band(bit32.rshift(d2, 26), 0xffffffff); h2 = bit32.band(d2, 0x3ffffff)
d3 = d3 + c; c = bit32.band(bit32.rshift(d3, 26), 0xffffffff); h3 = bit32.band(d3, 0x3ffffff)
d4 = d4 + c; c = bit32.band(bit32.rshift(d4, 26), 0xffffffff); h4 = bit32.band(d4, 0x3ffffff)
h0 = h0 + (c * 5); c = bit32.rshift(h0, 26); h0 = bit32.band(h0, 0x3ffffff)
h1 = h1 + c
--
midx = midx + 16 -- 16 = poly1305_block_size
bytes = bytes - 16
end --while
st.h[1] = h0
st.h[2] = h1
st.h[3] = h2
st.h[4] = h3
st.h[5] = h4
st.bytes = bytes -- remaining bytes. must be < 16 here
st.midx = midx -- index of first remaining bytes
return st
end --poly_blocks()
local function poly_update(st, m)
-- st: internal state
-- m: message:string
st.bytes, st.midx = #m, 1
-- process full blocks if any
if st.bytes >= 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
-- st: internal state
-- m: message:string
st.bytes, st.midx = #m, 1
-- process full blocks if any
if st.bytes >= 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('<I4I4I4I4', h0, h1, h2, h3)
-- (should zero out the state?)
--
return mac
--
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('<I4I4I4I4', h0, h1, h2, h3)
-- (should zero out the state?)
--
return mac
end --poly_finish()
local function poly_auth(m, k)
-- m: msg string
-- k: key string (must be 32 bytes)
-- return mac 16-byte string
assert(#k == 32)
local st = poly_init(k)
poly_update(st, m)
local mac = poly_finish(st)
return mac
-- m: msg string
-- k: key string (must be 32 bytes)
-- return mac 16-byte string
assert(#k == 32)
local st = poly_init(k)
poly_update(st, m)
local mac = poly_finish(st)
return mac
end --poly_auth()
local function poly_verify(m, k, mac)
local macm = poly_auth(m, k)
return macm == mac
local macm = poly_auth(m, k)
return macm == mac
end --poly_verify()
------------------------------------------------------------
-- return poly1305 module
return {
init = poly_init,
update = poly_update,
finish = poly_finish,
auth = poly_auth,
verify = poly_verify,
init = poly_init,
update = poly_update,
finish = poly_finish,
auth = poly_auth,
verify = poly_verify,
}

View file

@ -9,84 +9,84 @@ local expect = require "cc.expect".expect
-- Initialization code
local PrimeUI = {}
do
local coros = {}
local restoreCursor
local coros = {}
local restoreCursor
--- Adds a task to run in the main loop.
---@param func function The function to run, usually an `os.pullEvent` loop
function PrimeUI.addTask(func)
expect(1, func, "function")
local t = {coro = coroutine.create(func)}
coros[#coros+1] = t
_, t.filter = coroutine.resume(t.coro)
--- Adds a task to run in the main loop.
---@param func function The function to run, usually an `os.pullEvent` loop
function PrimeUI.addTask(func)
expect(1, func, "function")
local t = { coro = coroutine.create(func) }
coros[#coros + 1] = t
_, t.filter = coroutine.resume(t.coro)
end
--- Sends the provided arguments to the run loop, where they will be returned.
---@param ... any The parameters to send
function PrimeUI.resolve(...)
coroutine.yield(coros, ...)
end
--- Clears the screen and resets all components. Do not use any previously
--- created components after calling this function.
function PrimeUI.clear()
-- Reset the screen.
term.setCursorPos(1, 1)
term.setCursorBlink(false)
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
-- Reset the task list and cursor restore function.
coros = {}
restoreCursor = nil
end
--- Sets or clears the window that holds where the cursor should be.
---@param win window|nil The window to set as the active window
function PrimeUI.setCursorWindow(win)
expect(1, win, "table", "nil")
restoreCursor = win and win.restoreCursor
end
--- Gets the absolute position of a coordinate relative to a window.
---@param win window The window to check
---@param x number The relative X position of the point
---@param y number The relative Y position of the point
---@return number x The absolute X position of the window
---@return number y The absolute Y position of the window
function PrimeUI.getWindowPos(win, x, y)
if win == term then return x, y end
while win ~= term.native() and win ~= term.current() do
if not win.getPosition then return x, y end
local wx, wy = win.getPosition()
x, y = x + wx - 1, y + wy - 1
_, win = debug.getupvalue(select(2, debug.getupvalue(win.isColor, 1)), 1) -- gets the parent window through an upvalue
end
return x, y
end
--- Sends the provided arguments to the run loop, where they will be returned.
---@param ... any The parameters to send
function PrimeUI.resolve(...)
coroutine.yield(coros, ...)
end
--- Clears the screen and resets all components. Do not use any previously
--- created components after calling this function.
function PrimeUI.clear()
-- Reset the screen.
term.setCursorPos(1, 1)
term.setCursorBlink(false)
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
-- Reset the task list and cursor restore function.
coros = {}
restoreCursor = nil
end
--- Sets or clears the window that holds where the cursor should be.
---@param win window|nil The window to set as the active window
function PrimeUI.setCursorWindow(win)
expect(1, win, "table", "nil")
restoreCursor = win and win.restoreCursor
end
--- Gets the absolute position of a coordinate relative to a window.
---@param win window The window to check
---@param x number The relative X position of the point
---@param y number The relative Y position of the point
---@return number x The absolute X position of the window
---@return number y The absolute Y position of the window
function PrimeUI.getWindowPos(win, x, y)
if win == term then return x, y end
while win ~= term.native() and win ~= term.current() do
if not win.getPosition then return x, y end
local wx, wy = win.getPosition()
x, y = x + wx - 1, y + wy - 1
_, win = debug.getupvalue(select(2, debug.getupvalue(win.isColor, 1)), 1) -- gets the parent window through an upvalue
end
return x, y
end
--- Runs the main loop, returning information on an action.
---@return any ... The result of the coroutine that exited
function PrimeUI.run()
while true do
-- Restore the cursor and wait for the next event.
if restoreCursor then restoreCursor() end
local ev = table.pack(os.pullEvent())
-- Run all coroutines.
for _, v in ipairs(coros) do
if v.filter == nil or v.filter == ev[1] then
-- Resume the coroutine, passing the current event.
local res = table.pack(coroutine.resume(v.coro, table.unpack(ev, 1, ev.n)))
-- If the call failed, bail out. Coroutines should never exit.
if not res[1] then error(res[2], 2) end
-- If the coroutine resolved, return its values.
if res[2] == coros then return table.unpack(res, 3, res.n) end
-- Set the next event filter.
v.filter = res[2]
end
end
--- Runs the main loop, returning information on an action.
---@return any ... The result of the coroutine that exited
function PrimeUI.run()
while true do
-- Restore the cursor and wait for the next event.
if restoreCursor then restoreCursor() end
local ev = table.pack(os.pullEvent())
-- Run all coroutines.
for _, v in ipairs(coros) do
if v.filter == nil or v.filter == ev[1] then
-- Resume the coroutine, passing the current event.
local res = table.pack(coroutine.resume(v.coro, table.unpack(ev, 1, ev.n)))
-- If the call failed, bail out. Coroutines should never exit.
if not res[1] then error(res[2], 2) end
-- If the coroutine resolved, return its values.
if res[2] == coros then return table.unpack(res, 3, res.n) end
-- Set the next event filter.
v.filter = res[2]
end
end
end
end
end
--- Creates a text input box.
@ -102,102 +102,103 @@ end
---@param replacement string|nil A character to replace typed characters with
---@param history string[]|nil A list of previous entries to provide
---@param completion function|nil A function to call to provide completion
function PrimeUI.inputBox(win, x, y, width, action, placeholder, fgColor, bgColor, placeholderFg, replacement, history, completion, default)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, width, "number")
expect(5, action, "function", "string")
expect(6, placeholder, "string", "nil")
fgColor = expect(7, fgColor, "number", "nil") or colors.white
bgColor = expect(8, bgColor, "number", "nil") or colors.black
placeholderFg = expect(9, placeholderFg, "number", "nil") or colors.lightGray
expect(10, replacement, "string", "nil")
expect(11, history, "table", "nil")
expect(12, completion, "function", "nil")
expect(13, default, "string", "nil")
function PrimeUI.inputBox(win, x, y, width, action, placeholder, fgColor, bgColor, placeholderFg, replacement, history,
completion, default)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, width, "number")
expect(5, action, "function", "string")
expect(6, placeholder, "string", "nil")
fgColor = expect(7, fgColor, "number", "nil") or colors.white
bgColor = expect(8, bgColor, "number", "nil") or colors.black
placeholderFg = expect(9, placeholderFg, "number", "nil") or colors.lightGray
expect(10, replacement, "string", "nil")
expect(11, history, "table", "nil")
expect(12, completion, "function", "nil")
expect(13, default, "string", "nil")
local box = window.create(win, x, y, width, 1)
box.setTextColor(fgColor)
box.setBackgroundColor(bgColor)
box.clear()
local box = window.create(win, x, y, width, 1)
box.setTextColor(fgColor)
box.setBackgroundColor(bgColor)
box.clear()
PrimeUI.addTask(function()
local text = default or ""
local cursor = #text + 1
local histIndex = nil
PrimeUI.addTask(function()
local text = default or ""
local cursor = #text + 1
local histIndex = nil
local function runAction()
if type(action) == "string" then
PrimeUI.resolve("inputBox", action, text)
else
action(text)
end
local function runAction()
if type(action) == "string" then
PrimeUI.resolve("inputBox", action, text)
else
action(text)
end
end
local function redraw()
box.clear()
box.setCursorPos(1, 1)
if replacement then
box.write(string.rep(replacement, #text))
else
if #text == 0 and placeholder then
box.setTextColor(placeholderFg)
box.write(placeholder)
box.setTextColor(fgColor)
else
box.write(text)
end
end
box.setCursorPos(cursor, 1)
runAction()
end
local function redraw()
box.clear()
box.setCursorPos(1, 1)
if replacement then
box.write(string.rep(replacement, #text))
else
if #text == 0 and placeholder then
box.setTextColor(placeholderFg)
box.write(placeholder)
box.setTextColor(fgColor)
else
box.write(text)
end
end
box.setCursorPos(cursor, 1)
runAction()
end
redraw()
while true do
local ev, p1 = os.pullEvent()
if ev == "char" then
text = text:sub(1, cursor - 1) .. p1 .. text:sub(cursor)
cursor = cursor + 1
redraw()
while true do
local ev, p1 = os.pullEvent()
if ev == "char" then
text = text:sub(1, cursor - 1) .. p1 .. text:sub(cursor)
cursor = cursor + 1
redraw()
elseif ev == "key" then
if p1 == keys.enter then
runAction()
elseif p1 == keys.left then
cursor = math.max(1, cursor - 1)
box.setCursorPos(cursor, 1)
elseif p1 == keys.right then
cursor = math.min(#text + 1, cursor + 1)
box.setCursorPos(cursor, 1)
elseif p1 == keys.backspace then
if cursor > 1 then
text = text:sub(1, cursor - 2) .. text:sub(cursor)
cursor = cursor - 1
redraw()
end
elseif p1 == keys.delete then
text = text:sub(1, cursor - 1) .. text:sub(cursor + 1)
redraw()
elseif p1 == keys.up and history then
if not histIndex then histIndex = #history + 1 end
histIndex = math.max(1, histIndex - 1)
text = history[histIndex] or ""
cursor = #text + 1
redraw()
elseif p1 == keys.down and history then
if histIndex then
histIndex = math.min(#history + 1, histIndex + 1)
text = history[histIndex] or ""
cursor = #text + 1
redraw()
end
end
end
elseif ev == "key" then
if p1 == keys.enter then
runAction()
elseif p1 == keys.left then
cursor = math.max(1, cursor - 1)
box.setCursorPos(cursor, 1)
elseif p1 == keys.right then
cursor = math.min(#text + 1, cursor + 1)
box.setCursorPos(cursor, 1)
elseif p1 == keys.backspace then
if cursor > 1 then
text = text:sub(1, cursor - 2) .. text:sub(cursor)
cursor = cursor - 1
redraw()
end
elseif p1 == keys.delete then
text = text:sub(1, cursor - 1) .. text:sub(cursor + 1)
redraw()
elseif p1 == keys.up and history then
if not histIndex then histIndex = #history + 1 end
histIndex = math.max(1, histIndex - 1)
text = history[histIndex] or ""
cursor = #text + 1
redraw()
elseif p1 == keys.down and history then
if histIndex then
histIndex = math.min(#history + 1, histIndex + 1)
text = history[histIndex] or ""
cursor = #text + 1
redraw()
end
end
end)
end
end
end)
end
--- Draws a line of text at a position.
@ -208,16 +209,16 @@ end
---@param fgColor color|nil The color of the text (defaults to white)
---@param bgColor color|nil The color of the background (defaults to black)
function PrimeUI.label(win, x, y, text, fgColor, bgColor)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, text, "string")
fgColor = expect(5, fgColor, "number", "nil") or colors.white
bgColor = expect(6, bgColor, "number", "nil") or colors.black
win.setCursorPos(x, y)
win.setTextColor(fgColor)
win.setBackgroundColor(bgColor)
win.write(text)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, text, "string")
fgColor = expect(5, fgColor, "number", "nil") or colors.white
bgColor = expect(6, bgColor, "number", "nil") or colors.black
win.setCursorPos(x, y)
win.setTextColor(fgColor)
win.setBackgroundColor(bgColor)
win.write(text)
end
--- Creates a scrollable window, which allows drawing large content in a small area.
@ -234,88 +235,90 @@ end
---@return window inner The inner window to draw inside
---@return fun(pos:number) scroll A function to manually set the scroll position of the window
function PrimeUI.scrollBox(win, x, y, width, height, innerHeight, allowArrowKeys, showScrollIndicators, fgColor, bgColor)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, width, "number")
expect(5, height, "number")
expect(6, innerHeight, "number")
expect(7, allowArrowKeys, "boolean", "nil")
expect(8, showScrollIndicators, "boolean", "nil")
fgColor = expect(9, fgColor, "number", "nil") or colors.white
bgColor = expect(10, bgColor, "number", "nil") or colors.black
if allowArrowKeys == nil then allowArrowKeys = true end
-- Create the outer container box.
local outer = window.create(win == term and term.current() or win, x, y, width, height)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, width, "number")
expect(5, height, "number")
expect(6, innerHeight, "number")
expect(7, allowArrowKeys, "boolean", "nil")
expect(8, showScrollIndicators, "boolean", "nil")
fgColor = expect(9, fgColor, "number", "nil") or colors.white
bgColor = expect(10, bgColor, "number", "nil") or colors.black
if allowArrowKeys == nil then allowArrowKeys = true end
-- Create the outer container box.
local outer = window.create(win == term and term.current() or win, x, y, width, height)
outer.setBackgroundColor(bgColor)
outer.clear()
-- Create the inner scrolling box.
local inner = window.create(outer, 1, 1, width - (showScrollIndicators and 1 or 0), innerHeight)
inner.setBackgroundColor(bgColor)
inner.clear()
-- Draw scroll indicators if desired.
if showScrollIndicators then
outer.setBackgroundColor(bgColor)
outer.clear()
-- Create the inner scrolling box.
local inner = window.create(outer, 1, 1, width - (showScrollIndicators and 1 or 0), innerHeight)
inner.setBackgroundColor(bgColor)
inner.clear()
-- Draw scroll indicators if desired.
if showScrollIndicators then
outer.setTextColor(fgColor)
outer.setCursorPos(width, height)
outer.write(innerHeight > height and "\31" or " ")
end
-- Get the absolute position of the window.
x, y = PrimeUI.getWindowPos(win, x, y)
-- Add the scroll handler.
local scrollPos = 1
PrimeUI.addTask(function()
while true do
-- Wait for next event.
local ev = table.pack(os.pullEvent())
-- Update inner height in case it changed.
innerHeight = select(2, inner.getSize())
-- Check for scroll events and set direction.
local dir
if ev[1] == "key" and allowArrowKeys then
if ev[2] == keys.up then
dir = -1
elseif ev[2] == keys.down then
dir = 1
end
elseif ev[1] == "mouse_scroll" and ev[3] >= x and ev[3] < x + width and ev[4] >= y and ev[4] < y + height then
dir = ev[2]
end
-- If there's a scroll event, move the window vertically.
if dir and (scrollPos + dir >= 1 and scrollPos + dir <= innerHeight - height) then
scrollPos = scrollPos + dir
inner.reposition(1, 2 - scrollPos)
end
-- Redraw scroll indicators if desired.
if showScrollIndicators then
outer.setBackgroundColor(bgColor)
outer.setTextColor(fgColor)
outer.setCursorPos(width, 1)
outer.write(scrollPos > 1 and "\30" or " ")
outer.setCursorPos(width, height)
outer.write(innerHeight > height and "\31" or " ")
outer.write(scrollPos < innerHeight - height and "\31" or " ")
end
end
-- Get the absolute position of the window.
x, y = PrimeUI.getWindowPos(win, x, y)
-- Add the scroll handler.
local scrollPos = 1
PrimeUI.addTask(function()
while true do
-- Wait for next event.
local ev = table.pack(os.pullEvent())
-- Update inner height in case it changed.
innerHeight = select(2, inner.getSize())
-- Check for scroll events and set direction.
local dir
if ev[1] == "key" and allowArrowKeys then
if ev[2] == keys.up then dir = -1
elseif ev[2] == keys.down then dir = 1 end
elseif ev[1] == "mouse_scroll" and ev[3] >= x and ev[3] < x + width and ev[4] >= y and ev[4] < y + height then
dir = ev[2]
end
-- If there's a scroll event, move the window vertically.
if dir and (scrollPos + dir >= 1 and scrollPos + dir <= innerHeight - height) then
scrollPos = scrollPos + dir
inner.reposition(1, 2 - scrollPos)
end
-- Redraw scroll indicators if desired.
if showScrollIndicators then
outer.setBackgroundColor(bgColor)
outer.setTextColor(fgColor)
outer.setCursorPos(width, 1)
outer.write(scrollPos > 1 and "\30" or " ")
outer.setCursorPos(width, height)
outer.write(scrollPos < innerHeight - height and "\31" or " ")
end
end
end)
-- Make a function to allow external scrolling.
local function scroll(pos)
expect(1, pos, "number")
pos = math.floor(pos)
expect.range(pos, 1, innerHeight - height)
-- Scroll the window.
scrollPos = pos
inner.reposition(1, 2 - scrollPos)
-- Redraw scroll indicators if desired.
if showScrollIndicators then
outer.setBackgroundColor(bgColor)
outer.setTextColor(fgColor)
outer.setCursorPos(width, 1)
outer.write(scrollPos > 1 and "\30" or " ")
outer.setCursorPos(width, height)
outer.write(scrollPos < innerHeight - height and "\31" or " ")
end
end)
-- Make a function to allow external scrolling.
local function scroll(pos)
expect(1, pos, "number")
pos = math.floor(pos)
expect.range(pos, 1, innerHeight - height)
-- Scroll the window.
scrollPos = pos
inner.reposition(1, 2 - scrollPos)
-- Redraw scroll indicators if desired.
if showScrollIndicators then
outer.setBackgroundColor(bgColor)
outer.setTextColor(fgColor)
outer.setCursorPos(width, 1)
outer.write(scrollPos > 1 and "\30" or " ")
outer.setCursorPos(width, height)
outer.write(scrollPos < innerHeight - height and "\31" or " ")
end
return inner, scroll
end
return inner, scroll
end
--- Creates a list of entries that can each be selected.
---@param win window The window to draw on
---@param x number The X coordinate of the inside of the box
@ -328,152 +331,178 @@ end
---@param fgColor color|nil The color of the text (defaults to white)
---@param bgColor color|nil The color of the background (defaults to black)
function PrimeUI.selectionBox(win, x, y, width, height, entries, action, selectChangeAction, fgColor, bgColor)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, width, "number")
expect(5, height, "number")
expect(6, entries, "function")
expect(7, action, "function", "string")
expect(8, selectChangeAction, "function", "string", "nil")
fgColor = expect(9, fgColor, "number", "nil") or colors.white
bgColor = expect(10, bgColor, "number", "nil") or colors.black
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, width, "number")
expect(5, height, "number")
expect(6, entries, "function")
expect(7, action, "function", "string")
expect(8, selectChangeAction, "function", "string", "nil")
fgColor = expect(9, fgColor, "number", "nil") or colors.white
bgColor = expect(10, bgColor, "number", "nil") or colors.black
local entrywin = window.create(win, x, y, width, height)
local selection, scroll = 1, 1
-- Create a function to redraw the entries on screen.
local function drawEntries()
-- Clear and set invisible for performance.
entrywin.setVisible(false)
entrywin.setBackgroundColor(bgColor)
entrywin.clear()
-- Draw each entry in the scrolled region.
for i = scroll, scroll + height - 1 do
-- Get the entry; stop if there's no more.
local e = entries()[i]
if not e then break end
-- Set the colors: invert if selected.
entrywin.setCursorPos(2, i - scroll + 1)
if i == selection then
entrywin.setBackgroundColor(fgColor)
entrywin.setTextColor(bgColor)
else
entrywin.setBackgroundColor(bgColor)
entrywin.setTextColor(fgColor)
end
-- Draw the selection.
entrywin.clearLine()
entrywin.write(#e > width - 1 and e:sub(1, width - 4) .. "..." or e)
end
-- Draw scroll arrows.
local entrywin = window.create(win, x, y, width, height)
local selection, scroll = 1, 1
-- Create a function to redraw the entries on screen.
local function drawEntries()
-- Clear and set invisible for performance.
entrywin.setVisible(false)
entrywin.setBackgroundColor(bgColor)
entrywin.clear()
-- Draw each entry in the scrolled region.
for i = scroll, scroll + height - 1 do
-- Get the entry; stop if there's no more.
local e = entries()[i]
if not e then break end
-- Set the colors: invert if selected.
entrywin.setCursorPos(2, i - scroll + 1)
if i == selection then
entrywin.setBackgroundColor(fgColor)
entrywin.setTextColor(bgColor)
else
entrywin.setBackgroundColor(bgColor)
entrywin.setTextColor(fgColor)
entrywin.setCursorPos(width, 1)
entrywin.write("\30")
entrywin.setCursorPos(width, height)
entrywin.write("\31")
-- Send updates to the screen.
entrywin.setVisible(true)
end
-- Draw the selection.
entrywin.clearLine()
entrywin.write(#e > width - 1 and e:sub(1, width - 4) .. "..." or e)
end
-- Draw first screen.
drawEntries()
-- Add a task for selection keys.
PrimeUI.addTask(function()
while true do
local event, key, cx, cy = os.pullEvent()
if event == "key" then
if key == keys.down and selection < #entries() then
-- Move selection down.
selection = selection + 1
if selection > scroll + height - 1 then scroll = scroll + 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then selectChangeAction(selection) end
-- Redraw screen.
drawEntries()
elseif key == keys.up and selection > 1 then
-- Move selection up.
selection = selection - 1
if selection < scroll then scroll = scroll - 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then selectChangeAction(selection) end
-- Redraw screen.
drawEntries()
elseif key == keys.enter then
-- Select the entry: send the action.
if type(action) == "string" then PrimeUI.resolve("selectionBox", action, entries()[selection])
else action(entries()[selection]) end
end
elseif event == "mouse_click" and key == 1 then
-- Handle clicking the scroll arrows.
local wx, wy = PrimeUI.getWindowPos(entrywin, 1, 1)
if cx == wx + width - 1 then
if cy == wy and selection > 1 then
-- Move selection up.
selection = selection - 1
if selection < scroll then scroll = scroll - 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then selectChangeAction(selection) end
-- Redraw screen.
drawEntries()
elseif cy == wy + height - 1 and selection < #entries() then
-- Move selection down.
selection = selection + 1
if selection > scroll + height - 1 then scroll = scroll + 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then selectChangeAction(selection) end
-- Redraw screen.
drawEntries()
end
elseif cx >= wx and cx < wx + width - 1 and cy >= wy and cy < wy + height then
local sel = scroll + (cy - wy)
if sel == selection then
-- Select the entry: send the action.
if type(action) == "string" then PrimeUI.resolve("selectionBox", action, entries()[selection])
else action(entries()[selection]) end
else
selection = sel
-- Send action if necessary.
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then selectChangeAction(selection) end
-- Redraw screen.
drawEntries()
end
end
elseif event == "mouse_scroll" then
-- Handle mouse scrolling.
local wx, wy = PrimeUI.getWindowPos(entrywin, 1, 1)
if cx >= wx and cx < wx + width and cy >= wy and cy < wy + height then
if key < 0 and selection > 1 then
-- Move selection up.
selection = selection - 1
if selection < scroll then scroll = scroll - 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then selectChangeAction(selection) end
-- Redraw screen.
drawEntries()
elseif key > 0 and selection < #entries() then
-- Move selection down.
selection = selection + 1
if selection > scroll + height - 1 then scroll = scroll + 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then selectChangeAction(selection) end
-- Redraw screen.
drawEntries()
end
end
end
-- Draw scroll arrows.
entrywin.setBackgroundColor(bgColor)
entrywin.setTextColor(fgColor)
entrywin.setCursorPos(width, 1)
entrywin.write("\30")
entrywin.setCursorPos(width, height)
entrywin.write("\31")
-- Send updates to the screen.
entrywin.setVisible(true)
end
-- Draw first screen.
drawEntries()
-- Add a task for selection keys.
PrimeUI.addTask(function()
while true do
local event, key, cx, cy = os.pullEvent()
if event == "key" then
if key == keys.down and selection < #entries() then
-- Move selection down.
selection = selection + 1
if selection > scroll + height - 1 then scroll = scroll + 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then
selectChangeAction(selection)
end
-- Redraw screen.
drawEntries()
elseif key == keys.up and selection > 1 then
-- Move selection up.
selection = selection - 1
if selection < scroll then scroll = scroll - 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then
selectChangeAction(selection)
end
-- Redraw screen.
drawEntries()
elseif key == keys.enter then
-- Select the entry: send the action.
if type(action) == "string" then
PrimeUI.resolve("selectionBox", action, entries()[selection])
else
action(entries()[selection])
end
end
end)
return drawEntries
elseif event == "mouse_click" and key == 1 then
-- Handle clicking the scroll arrows.
local wx, wy = PrimeUI.getWindowPos(entrywin, 1, 1)
if cx == wx + width - 1 then
if cy == wy and selection > 1 then
-- Move selection up.
selection = selection - 1
if selection < scroll then scroll = scroll - 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then
selectChangeAction(selection)
end
-- Redraw screen.
drawEntries()
elseif cy == wy + height - 1 and selection < #entries() then
-- Move selection down.
selection = selection + 1
if selection > scroll + height - 1 then scroll = scroll + 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then
selectChangeAction(selection)
end
-- Redraw screen.
drawEntries()
end
elseif cx >= wx and cx < wx + width - 1 and cy >= wy and cy < wy + height then
local sel = scroll + (cy - wy)
if sel == selection then
-- Select the entry: send the action.
if type(action) == "string" then
PrimeUI.resolve("selectionBox", action, entries()[selection])
else
action(entries()[selection])
end
else
selection = sel
-- Send action if necessary.
if type(selectChangeAction) == "string" then
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then
selectChangeAction(selection)
end
-- Redraw screen.
drawEntries()
end
end
elseif event == "mouse_scroll" then
-- Handle mouse scrolling.
local wx, wy = PrimeUI.getWindowPos(entrywin, 1, 1)
if cx >= wx and cx < wx + width and cy >= wy and cy < wy + height then
if key < 0 and selection > 1 then
-- Move selection up.
selection = selection - 1
if selection < scroll then scroll = scroll - 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then
selectChangeAction(selection)
end
-- Redraw screen.
drawEntries()
elseif key > 0 and selection < #entries() then
-- Move selection down.
selection = selection + 1
if selection > scroll + height - 1 then scroll = scroll + 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then
selectChangeAction(selection)
end
-- Redraw screen.
drawEntries()
end
end
end
end
end)
return drawEntries
end
return {
PrimeUI = PrimeUI
PrimeUI = PrimeUI
}