Skip to content

Instantly share code, notes, and snippets.

@MCJack123
Last active April 2, 2024 11:59
Show Gist options
  • Save MCJack123/7752c85918bcf23ada028abd615e8750 to your computer and use it in GitHub Desktop.
Save MCJack123/7752c85918bcf23ada028abd615e8750 to your computer and use it in GitHub Desktop.
VeriCode - Easy code signing for ComputerCraft
--- VeriCode - Easy code signing for ComputerCraft
-- By JackMacWindows
--
-- @module vericode
--
-- Code signing uses encryption and hashes to easily verify a) that the sender of
-- the code is trusted, and b) that the code hasn't been changed mid-transfer.
-- VeriCode applies this concept to Lua code sent over Rednet to add a layer of
-- security to Rednet. Just plainly receiving code from whoever sends it is
-- dangerous, and invites the possibility of getting malware (in fact, I've made
-- a virus that spreads through this method). Adding code signing ensures that
-- any code received is safe and trusted.
--
-- Requires ecc library (pastebin get ZGJGBJdg ecc.lua)
--[[ Basic usage:
1. Generate keypair files with vericode.generateKeypair
2. Copy the .key.pub file (NOT the standard .key file!!!) to each client that
needs to receive signed code
3. Require the API & load the key (.key on server, .key.pub on clients) - on the
server, make sure to store the key returned from loadKey as you'll need it to send
4. Call vericode.send to send a Lua script to a client computer
5. Call vericode.receive on the client to listen for code from the server (note
that it returns after receiving a function, so call it in an infinite loop if
you want it to always accept code)
Example code:
-- On server:
local vericode = require "vericode"
if not fs.exists("mykey.key") then
vericode.generateKeypair("mykey.key")
print("Please copy mykey.key.pub to the client computer.")
return
end
local key = vericode.loadKey("mykey.key")
vericode.send(otherComputerID, "turtle.forward()", key, "turtleInstructions")
-- On client:
local vericode = require "vericode"
vericode.loadKey("mykey.key.pub")
while true do vericode.receive(true, "turtleInstructions") end
--]]
-- MIT License
--
-- Copyright (c) 2021 JackMacWindows
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
local function minver(version)
local res
if _CC_VERSION then res = version <= _CC_VERSION
elseif not _HOST then res = version <= os.version():gsub("CraftOS ", "")
elseif _HOST:match("ComputerCraft 1%.1%d+") ~= version:match("1%.1%d+") then
version = version:gsub("(1%.)([02-9])", "%10%2")
local host = _HOST:gsub("(ComputerCraft 1%.)([02-9])", "%10%2")
res = version <= host:match("ComputerCraft ([0-9%.]+)")
else res = version <= _HOST:match("ComputerCraft ([0-9%.]+)") end
assert(res, "This program requires ComputerCraft " .. version .. " or later.")
end
minver "1.91.0" -- string.pack, string.unpack
if _VERSION ~= "Lua 5.1" then error("This version of VeriCode only works with Lua 5.1.") end
local expect = require "cc.expect".expect
local ecc = require "ecc"
local vericode = {}
local keyStore = {}
local b64str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
local function base64encode(str)
local retval = ""
for s in str:gmatch "..." do
local n = s:byte(1) * 65536 + s:byte(2) * 256 + s:byte(3)
local a, b, c, d = bit32.extract(n, 18, 6), bit32.extract(n, 12, 6), bit32.extract(n, 6, 6), bit32.extract(n, 0, 6)
retval = retval .. b64str:sub(a+1, a+1) .. b64str:sub(b+1, b+1) .. b64str:sub(c+1, c+1) .. b64str:sub(d+1, d+1)
end
if #str % 3 == 1 then
local n = str:byte(-1)
local a, b = bit32.rshift(n, 2), bit32.lshift(bit32.band(n, 3), 4)
retval = retval .. b64str:sub(a+1, a+1) .. b64str:sub(b+1, b+1) .. "=="
elseif #str % 3 == 2 then
local n = str:byte(-2) * 256 + str:byte(-1)
local a, b, c, d = bit32.extract(n, 10, 6), bit32.extract(n, 4, 6), bit32.lshift(bit32.extract(n, 0, 4), 2)
retval = retval .. b64str:sub(a+1, a+1) .. b64str:sub(b+1, b+1) .. b64str:sub(c+1, c+1) .. "="
end
return retval
end
local function base64decode(str)
local retval = ""
for s in str:gmatch "...." do
if s:sub(3, 4) == '==' then
retval = retval .. string.char(bit32.bor(bit32.lshift(b64str:find(s:sub(1, 1)) - 1, 2), bit32.rshift(b64str:find(s:sub(2, 2)) - 1, 4)))
elseif s:sub(4, 4) == '=' then
local n = (b64str:find(s:sub(1, 1))-1) * 4096 + (b64str:find(s:sub(2, 2))-1) * 64 + (b64str:find(s:sub(3, 3))-1)
retval = retval .. string.char(bit32.extract(n, 10, 8)) .. string.char(bit32.extract(n, 2, 8))
else
local n = (b64str:find(s:sub(1, 1))-1) * 262144 + (b64str:find(s:sub(2, 2))-1) * 4096 + (b64str:find(s:sub(3, 3))-1) * 64 + (b64str:find(s:sub(4, 4))-1)
retval = retval .. string.char(bit32.extract(n, 16, 8)) .. string.char(bit32.extract(n, 8, 8)) .. string.char(bit32.extract(n, 0, 8))
end
end
return retval
end
vericode.base64 = {encode = base64encode, decode = base64decode}
vericode.sha256 = ecc.sha256
vericode.random = ecc.random
vericode.ecc = ecc
--- Generates a keypair for code signing.
-- Outputs a pub/priv keypair at `path`, and a public-only key (for receivers) at `path`.pub.
-- The generated key will be added to the store.
-- @param path string The path to the file to generate.
-- @return string pub The new public key.
-- @return string priv The new private key. (Not required for this API, but might be useful otherwise.)
function vericode.generateKeypair(path)
expect(1, path, "string")
local priv, pub = ecc.keypair(ecc.random.random())
pub, priv = base64encode(string.char(table.unpack(pub))), base64encode(string.char(table.unpack(priv)))
local file, err = fs.open(path, "w")
if not file then error("Could not open certificate file: " .. err, 2) end
file.write(textutils.serialize({
public = pub,
private = priv
}))
file.close()
file, err = fs.open(path .. ".pub", "w")
if not file then error("Could not open public certificate file: " .. err, 2) end
file.write(textutils.serialize({
public = pub
}))
file.close()
keyStore[pub] = {
public = pub,
private = priv
}
return pub, priv
end
--- Loads a key from disk. This can be a full keypair, or only a public key.
-- @param path string The path to the key.
-- @return string key The loaded public key.
function vericode.loadKey(path)
expect(1, path, "string")
local file, err = fs.open(path, "r")
if not file then error("Could not open certificate file: " .. err, 2) end
local t = textutils.unserialize(file.readAll())
file.close()
if type(t) ~= "table" or t.public == nil then error("Invalid certificate file", 2) end
keyStore[t.public] = t
return t.public
end
--- Adds a public (and private if provided) key to the key store.
-- @param pub string The public key to add.
-- @param priv string|nil The private key to add, if desired.
function vericode.addKey(pub, priv)
expect(1, pub, "string")
expect(2, priv, "string", "nil")
keyStore[pub] = {
public = pub,
private = priv
}
end
--- Compiles, dumps, and signs a Lua chunk.
-- @param code string The Lua code to compile.
-- @param key string The public or private key to use. The private key associated with this key must exist in the key store.
-- @return string chunk A signed and compiled Lua chunk. This chunk can either be loaded with `load` here, or standard Lua `load`.
function vericode.dump(code, key)
expect(1, code, "string")
expect(2, key, "string")
local pub, priv
if keyStore[key] then
if not keyStore[key].private then error("No private key associated with selected public key", 2) end
pub, priv = key, keyStore[key].private
else
for _,v in pairs(keyStore) do
if v.public == key then
if not v.private then error("No private key associated with selected public key", 2) end
pub, priv = key, v.private
break
elseif v.private == key then
pub, priv = v.public, key
break
end
end
end
if not pub or not priv then error("Could not find private key", 2) end
local fn, err = load(code, "=temp")
if not fn then error("Could not load chunk: " .. err, 2) end
local dump = string.dump(fn)
local size_t = dump:byte(9)
local chunk = dump:sub(19 + size_t)
local name = "=signed-chunk:" .. pub .. ":" .. base64encode(string.char(table.unpack(ecc.sign(base64decode(priv), chunk)))) .. "\0"
return dump:sub(1, 12) .. string.pack("T" .. size_t, #name) .. name .. chunk
end
--- Loads and verifies a previously signed code chunk.
-- The public key associated with the chunk must be present in the key store.
-- @param code string The code chunk to load.
-- @param name string|nil The name of the chunk.
-- @param _mode nil Ignored (for compatibility).
-- @param env table|nil The environment to give the chunk.
-- @return function|nil fn The returned function, or nil on error.
-- @return nil|string err If an error occurred, the error message.
function vericode.load(code, name, _mode, env)
expect(1, code, "string")
expect(2, name, "string", "nil")
expect(4, env, "table", "nil")
if code:sub(1, 5) ~= "\x1bLuaQ" then return nil, "Not a compiled Lua chunk" end
local size_t = code:byte(9)
local codename = code:sub(13 + size_t, 12 + size_t + string.unpack("T" .. size_t, code:sub(13, 12 + size_t)))
local chunk = code:sub(13 + size_t + #codename)
local key, sig = codename:match "^=signed%-chunk:([A-Za-z0-9+/]+=*):([A-Za-z0-9+/]+=*)\0$"
if not key or not sig then return nil, "Not signed" end
if not keyStore[key] then return nil, "Unrecognized key: " .. key end
if not ecc.verify(base64decode(key), chunk, {base64decode(sig):byte(1, -1)}) then return nil, "Invalid code signature" end
if name then code = code:sub(1, 12) .. string.pack("T" .. size_t, #name + 1) .. name .. "\0" .. chunk end
return load(code, name, "b", env)
end
--- Sends a signed code chunk over Rednet.
-- @param recipient number The ID of the recipient.
-- @param code string The code chunk to send.
-- @param key string The key to use to sign the chunk.
-- @param protocol string|nil The protocol to set, if desired.
-- @return boolean ok Whether the message was sent.
function vericode.send(recipient, code, key, protocol)
expect(1, recipient, "number")
expect(2, code, "string")
expect(3, key, "string")
expect(4, protocol, "string", "nil")
return rednet.send(recipient, vericode.dump(code, key), protocol)
end
--- Waits to receive a signed code chunk, and either returns the loaded function or the results from calling it.
-- @param run boolean|nil Whether to run the code, or just return the function.
-- @param filter string|nil The name of the protocol to listen for (nil for any).
-- @param timeout number|nil The maximum amount of time to wait.
-- @param name string|nil The name to give the loaded chunk (defaults to "=VeriCode chunk").
-- @param env table|nil The environment to give the function.
-- @return any res Either the loaded function, or the results from the function, or nil if the timeout was passed.
function vericode.receive(run, filter, timeout, name, env)
expect(1, run, "boolean", "nil")
expect(2, filter, "string", "nil")
expect(3, timeout, "number", "nil")
expect(4, name, "string", "nil")
expect(5, env, "table", "nil")
local res = {n = 0}
local function receive()
while true do
local _, message = rednet.receive(filter)
if type(message) == "string" then
local fn = vericode.load(message, name or "=VeriCode chunk", nil, env)
if fn then
if run then res = table.pack(fn())
else res = {fn, n = 1} end
return table.unpack(res, 1, res.n)
end
end
end
end
if timeout then
parallel.waitForAny(receive, function() sleep(timeout) end)
return table.unpack(res, 1, res.n)
else return receive() end
end
if ... then vericode.generateKeypair(...) end
return vericode
-- Elliptic Curve Cryptography in Computercraft
---- Update (Jul 30 2020)
-- Make randomModQ and use it instead of hashing from random.random()
---- Update (Feb 10 2020)
-- Make a more robust encoding/decoding implementation
---- Update (Dec 30 2019)
-- Fix rng not accumulating entropy from loop
-- (older versions should be fine from other sources + stored in disk)
---- Update (Dec 28 2019)
-- Slightly better integer multiplication and squaring
-- Fix global variable declarations in modQ division and verify() (no security concerns)
-- Small tweaks from SquidDev's illuaminate (https://github.com/SquidDev/illuaminate/)
local byteTableMT = {
__tostring = function(a) return string.char(unpack(a)) end,
__index = {
toHex = function(self) return ("%02x"):rep(#self):format(unpack(self)) end,
isEqual = function(self, t)
if type(t) ~= "table" then return false end
if #self ~= #t then return false end
local ret = 0
for i = 1, #self do
ret = bit32.bor(ret, bit32.bxor(self[i], t[i]))
end
return ret == 0
end
}
}
-- SHA-256, HMAC and PBKDF2 functions in ComputerCraft
-- By Anavrins
-- For help and details, you can PM me on the CC forums
-- You may use this code in your projects without asking me, as long as credit is given and this header is kept intact
-- http://www.computercraft.info/forums2/index.php?/user/12870-anavrins
-- http://pastebin.com/6UV4qfNF
-- Last update: October 10, 2017
local sha256 = (function()
local mod32 = 2^32
local band = bit32 and bit32.band or bit.band
local bnot = bit32 and bit32.bnot or bit.bnot
local bxor = bit32 and bit32.bxor or bit.bxor
local blshift = bit32 and bit32.lshift or bit.blshift
local upack = unpack
local function rrotate(n, b)
local s = n/(2^b)
local f = s%1
return (s-f) + f*mod32
end
local function brshift(int, by) -- Thanks bit32 for bad rshift
local s = int / (2^by)
return s - s%1
end
local H = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
}
local K = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
}
local function counter(incr)
local t1, t2 = 0, 0
if 0xFFFFFFFF - t1 < incr then
t2 = t2 + 1
t1 = incr - (0xFFFFFFFF - t1) - 1
else t1 = t1 + incr
end
return t2, t1
end
local function BE_toInt(bs, i)
return blshift((bs[i] or 0), 24) + blshift((bs[i+1] or 0), 16) + blshift((bs[i+2] or 0), 8) + (bs[i+3] or 0)
end
local function preprocess(data)
local len = #data
local proc = {}
data[#data+1] = 0x80
while #data%64~=56 do data[#data+1] = 0 end
local blocks = math.ceil(#data/64)
for i = 1, blocks do
proc[i] = {}
for j = 1, 16 do
proc[i][j] = BE_toInt(data, 1+((i-1)*64)+((j-1)*4))
end
end
proc[blocks][15], proc[blocks][16] = counter(len*8)
return proc
end
local function digestblock(w, C)
for j = 17, 64 do
local s0 = bxor(bxor(rrotate(w[j-15], 7), rrotate(w[j-15], 18)), brshift(w[j-15], 3))
local s1 = bxor(bxor(rrotate(w[j-2], 17), rrotate(w[j-2], 19)), brshift(w[j-2], 10))
w[j] = (w[j-16] + s0 + w[j-7] + s1)%mod32
end
local a, b, c, d, e, f, g, h = upack(C)
for j = 1, 64 do
local S1 = bxor(bxor(rrotate(e, 6), rrotate(e, 11)), rrotate(e, 25))
local ch = bxor(band(e, f), band(bnot(e), g))
local temp1 = (h + S1 + ch + K[j] + w[j])%mod32
local S0 = bxor(bxor(rrotate(a, 2), rrotate(a, 13)), rrotate(a, 22))
local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c))
local temp2 = (S0 + maj)%mod32
h, g, f, e, d, c, b, a = g, f, e, (d+temp1)%mod32, c, b, a, (temp1+temp2)%mod32
end
C[1] = (C[1] + a)%mod32
C[2] = (C[2] + b)%mod32
C[3] = (C[3] + c)%mod32
C[4] = (C[4] + d)%mod32
C[5] = (C[5] + e)%mod32
C[6] = (C[6] + f)%mod32
C[7] = (C[7] + g)%mod32
C[8] = (C[8] + h)%mod32
return C
end
local function toBytes(t, n)
local b = {}
for i = 1, n do
b[(i-1)*4+1] = band(brshift(t[i], 24), 0xFF)
b[(i-1)*4+2] = band(brshift(t[i], 16), 0xFF)
b[(i-1)*4+3] = band(brshift(t[i], 8), 0xFF)
b[(i-1)*4+4] = band(t[i], 0xFF)
end
return setmetatable(b, byteTableMT)
end
local function digest(data)
data = data or ""
data = type(data) == "table" and {upack(data)} or {tostring(data):byte(1,-1)}
data = preprocess(data)
local C = {upack(H)}
for i = 1, #data do C = digestblock(data[i], C) end
return toBytes(C, 8)
end
local function hmac(data, key)
local data = type(data) == "table" and {upack(data)} or {tostring(data):byte(1,-1)}
local key = type(key) == "table" and {upack(key)} or {tostring(key):byte(1,-1)}
local blocksize = 64
key = #key > blocksize and digest(key) or key
local ipad = {}
local opad = {}
local padded_key = {}
for i = 1, blocksize do
ipad[i] = bxor(0x36, key[i] or 0)
opad[i] = bxor(0x5C, key[i] or 0)
end
for i = 1, #data do
ipad[blocksize+i] = data[i]
end
ipad = digest(ipad)
for i = 1, blocksize do
padded_key[i] = opad[i]
padded_key[blocksize+i] = ipad[i]
end
return digest(padded_key)
end
local function pbkdf2(pass, salt, iter, dklen)
local salt = type(salt) == "table" and salt or {tostring(salt):byte(1,-1)}
local hashlen = 32
local dklen = dklen or 32
local block = 1
local out = {}
while dklen > 0 do
local ikey = {}
local isalt = {upack(salt)}
local clen = dklen > hashlen and hashlen or dklen
isalt[#isalt+1] = band(brshift(block, 24), 0xFF)
isalt[#isalt+1] = band(brshift(block, 16), 0xFF)
isalt[#isalt+1] = band(brshift(block, 8), 0xFF)
isalt[#isalt+1] = band(block, 0xFF)
for j = 1, iter do
isalt = hmac(isalt, pass)
for k = 1, clen do ikey[k] = bxor(isalt[k], ikey[k] or 0) end
if j % 200 == 0 then os.queueEvent("PBKDF2", j) coroutine.yield("PBKDF2") end
end
dklen = dklen - clen
block = block+1
for k = 1, clen do out[#out+1] = ikey[k] end
end
return setmetatable(out, byteTableMT)
end
return {
digest = digest,
hmac = hmac,
pbkdf2 = pbkdf2
}
end)()
-- Chacha20 cipher in ComputerCraft
-- By Anavrins
-- For help and details, you can PM me on the CC forums
-- You may use this code in your projects without asking me, as long as credit is given and this header is kept intact
-- http://www.computercraft.info/forums2/index.php?/user/12870-anavrins
-- http://pastebin.com/GPzf9JSa
-- Last update: April 17, 2017
local chacha20 = (function()
local bxor = bit32.bxor
local band = bit32.band
local blshift = bit32.lshift
local brshift = bit32.arshift
local mod = 2^32
local tau = {("expand 16-byte k"):byte(1,-1)}
local sigma = {("expand 32-byte k"):byte(1,-1)}
local function rotl(n, b)
local s = n/(2^(32-b))
local f = s%1
return (s-f) + f*mod
end
local function quarterRound(s, a, b, c, d)
s[a] = (s[a]+s[b])%mod; s[d] = rotl(bxor(s[d], s[a]), 16)
s[c] = (s[c]+s[d])%mod; s[b] = rotl(bxor(s[b], s[c]), 12)
s[a] = (s[a]+s[b])%mod; s[d] = rotl(bxor(s[d], s[a]), 8)
s[c] = (s[c]+s[d])%mod; s[b] = rotl(bxor(s[b], s[c]), 7)
return s
end
local function hashBlock(state, rnd)
local s = {unpack(state)}
for i = 1, rnd do
local r = i%2==1
s = r and quarterRound(s, 1, 5, 9, 13) or quarterRound(s, 1, 6, 11, 16)
s = r and quarterRound(s, 2, 6, 10, 14) or quarterRound(s, 2, 7, 12, 13)
s = r and quarterRound(s, 3, 7, 11, 15) or quarterRound(s, 3, 8, 9, 14)
s = r and quarterRound(s, 4, 8, 12, 16) or quarterRound(s, 4, 5, 10, 15)
end
for i = 1, 16 do s[i] = (s[i]+state[i])%mod end
return s
end
local function LE_toInt(bs, i)
return (bs[i+1] or 0)+
blshift((bs[i+2] or 0), 8)+
blshift((bs[i+3] or 0), 16)+
blshift((bs[i+4] or 0), 24)
end
local function initState(key, nonce, counter)
local isKey256 = #key == 32
local const = isKey256 and sigma or tau
local state = {}
state[ 1] = LE_toInt(const, 0)
state[ 2] = LE_toInt(const, 4)
state[ 3] = LE_toInt(const, 8)
state[ 4] = LE_toInt(const, 12)
state[ 5] = LE_toInt(key, 0)
state[ 6] = LE_toInt(key, 4)
state[ 7] = LE_toInt(key, 8)
state[ 8] = LE_toInt(key, 12)
state[ 9] = LE_toInt(key, isKey256 and 16 or 0)
state[10] = LE_toInt(key, isKey256 and 20 or 4)
state[11] = LE_toInt(key, isKey256 and 24 or 8)
state[12] = LE_toInt(key, isKey256 and 28 or 12)
state[13] = counter
state[14] = LE_toInt(nonce, 0)
state[15] = LE_toInt(nonce, 4)
state[16] = LE_toInt(nonce, 8)
return state
end
local function serialize(state)
local r = {}
for i = 1, 16 do
r[#r+1] = band(state[i], 0xFF)
r[#r+1] = band(brshift(state[i], 8), 0xFF)
r[#r+1] = band(brshift(state[i], 16), 0xFF)
r[#r+1] = band(brshift(state[i], 24), 0xFF)
end
return r
end
local function crypt(data, key, nonce, cntr, round)
assert(type(key) == "table", "ChaCha20: Invalid key format ("..type(key).."), must be table")
assert(type(nonce) == "table", "ChaCha20: Invalid nonce format ("..type(nonce).."), must be table")
assert(#key == 16 or #key == 32, "ChaCha20: Invalid key length ("..#key.."), must be 16 or 32")
assert(#nonce == 12, "ChaCha20: Invalid nonce length ("..#nonce.."), must be 12")
local data = type(data) == "table" and {unpack(data)} or {tostring(data):byte(1,-1)}
cntr = tonumber(cntr) or 1
round = tonumber(round) or 20
local out = {}
local state = initState(key, nonce, cntr)
local blockAmt = math.floor(#data/64)
for i = 0, blockAmt do
local ks = serialize(hashBlock(state, round))
state[13] = (state[13]+1) % mod
local block = {}
for j = 1, 64 do
block[j] = data[((i)*64)+j]
end
for j = 1, #block do
out[#out+1] = bxor(block[j], ks[j])
end
if i % 1000 == 0 then
os.queueEvent("")
os.pullEvent("")
end
end
return setmetatable(out, byteTableMT)
end
return {
crypt = crypt
}
end)()
-- random.lua - Random Byte Generator
local random = (function()
local entropy = ""
local accumulator = ""
local entropyPath = "/.random"
local function feed(data)
accumulator = accumulator .. (data or "")
end
local function digest()
entropy = tostring(sha256.digest(entropy .. accumulator))
accumulator = ""
end
if fs.exists(entropyPath) then
local entropyFile = fs.open(entropyPath, "rb")
feed(entropyFile.readAll())
entropyFile.close()
end
feed("init")
feed(tostring(math.random(1, 2^31 - 1)))
feed("|")
feed(tostring(math.random(1, 2^31 - 1)))
feed("|")
feed(tostring(math.random(1, 2^4)))
feed("|")
feed(tostring(os.epoch("utc")))
feed("|")
for i = 1, 10000 do
local s = tostring({}):sub(8)
while #s < 8 do
s = "0" .. s
end
feed(string.char(os.epoch("utc") % 256))
feed(string.char(tonumber(s:sub(1, 2), 16)))
feed(string.char(tonumber(s:sub(3, 4), 16)))
feed(string.char(tonumber(s:sub(5, 6), 16)))
feed(string.char(tonumber(s:sub(7, 8), 16)))
end
digest()
feed(tostring(os.epoch("utc")))
digest()
local function save()
feed("save")
feed(tostring(os.epoch("utc")))
feed(tostring({}))
digest()
local entropyFile = fs.open(entropyPath, "wb")
entropyFile.write(tostring(sha256.hmac("save", entropy)))
entropy = tostring(sha256.digest(entropy))
entropyFile.close()
end
save()
local function seed(data)
feed("seed")
feed(tostring(os.epoch("utc")))
feed(tostring({}))
feed(data)
digest()
save()
end
local function random()
feed("random")
feed(tostring(os.epoch("utc")))
feed(tostring({}))
digest()
save()
local result = sha256.hmac("out", entropy)
entropy = tostring(sha256.digest(entropy))
return result
end
return {
seed = seed,
save = save,
random = random
}
end)()
-- Big integer arithmetic for 168-bit (and 336-bit) numbers
-- Numbers are represented as little-endian tables of 24-bit integers
local arith = (function()
local function isEqual(a, b)
return (
a[1] == b[1]
and a[2] == b[2]
and a[3] == b[3]
and a[4] == b[4]
and a[5] == b[5]
and a[6] == b[6]
and a[7] == b[7]
)
end
local function compare(a, b)
for i = 7, 1, -1 do
if a[i] > b[i] then
return 1
elseif a[i] < b[i] then
return -1
end
end
return 0
end
local function add(a, b)
-- c7 may be greater than 2^24 before reduction
local c1 = a[1] + b[1]
local c2 = a[2] + b[2]
local c3 = a[3] + b[3]
local c4 = a[4] + b[4]
local c5 = a[5] + b[5]
local c6 = a[6] + b[6]
local c7 = a[7] + b[7]
if c1 > 0xffffff then
c2 = c2 + 1
c1 = c1 - 0x1000000
end
if c2 > 0xffffff then
c3 = c3 + 1
c2 = c2 - 0x1000000
end
if c3 > 0xffffff then
c4 = c4 + 1
c3 = c3 - 0x1000000
end
if c4 > 0xffffff then
c5 = c5 + 1
c4 = c4 - 0x1000000
end
if c5 > 0xffffff then
c6 = c6 + 1
c5 = c5 - 0x1000000
end
if c6 > 0xffffff then
c7 = c7 + 1
c6 = c6 - 0x1000000
end
return {c1, c2, c3, c4, c5, c6, c7}
end
local function sub(a, b)
-- c7 may be negative before reduction
local c1 = a[1] - b[1]
local c2 = a[2] - b[2]
local c3 = a[3] - b[3]
local c4 = a[4] - b[4]
local c5 = a[5] - b[5]
local c6 = a[6] - b[6]
local c7 = a[7] - b[7]
if c1 < 0 then
c2 = c2 - 1
c1 = c1 + 0x1000000
end
if c2 < 0 then
c3 = c3 - 1
c2 = c2 + 0x1000000
end
if c3 < 0 then
c4 = c4 - 1
c3 = c3 + 0x1000000
end
if c4 < 0 then
c5 = c5 - 1
c4 = c4 + 0x1000000
end
if c5 < 0 then
c6 = c6 - 1
c5 = c5 + 0x1000000
end
if c6 < 0 then
c7 = c7 - 1
c6 = c6 + 0x1000000
end
return {c1, c2, c3, c4, c5, c6, c7}
end
local function rShift(a)
local c1 = a[1]
local c2 = a[2]
local c3 = a[3]
local c4 = a[4]
local c5 = a[5]
local c6 = a[6]
local c7 = a[7]
c1 = c1 / 2
c1 = c1 - c1 % 1
c1 = c1 + (c2 % 2) * 0x800000
c2 = c2 / 2
c2 = c2 - c2 % 1
c2 = c2 + (c3 % 2) * 0x800000
c3 = c3 / 2
c3 = c3 - c3 % 1
c3 = c3 + (c4 % 2) * 0x800000
c4 = c4 / 2
c4 = c4 - c4 % 1
c4 = c4 + (c5 % 2) * 0x800000
c5 = c5 / 2
c5 = c5 - c5 % 1
c5 = c5 + (c6 % 2) * 0x800000
c6 = c6 / 2
c6 = c6 - c6 % 1
c6 = c6 + (c7 % 2) * 0x800000
c7 = c7 / 2
c7 = c7 - c7 % 1
return {c1, c2, c3, c4, c5, c6, c7}
end
local function addDouble(a, b)
-- a and b are 336-bit integers (14 words)
local c1 = a[1] + b[1]
local c2 = a[2] + b[2]
local c3 = a[3] + b[3]
local c4 = a[4] + b[4]
local c5 = a[5] + b[5]
local c6 = a[6] + b[6]
local c7 = a[7] + b[7]
local c8 = a[8] + b[8]
local c9 = a[9] + b[9]
local c10 = a[10] + b[10]
local c11 = a[11] + b[11]
local c12 = a[12] + b[12]
local c13 = a[13] + b[13]
local c14 = a[14] + b[14]
if c1 > 0xffffff then
c2 = c2 + 1
c1 = c1 - 0x1000000
end
if c2 > 0xffffff then
c3 = c3 + 1
c2 = c2 - 0x1000000
end
if c3 > 0xffffff then
c4 = c4 + 1
c3 = c3 - 0x1000000
end
if c4 > 0xffffff then
c5 = c5 + 1
c4 = c4 - 0x1000000
end
if c5 > 0xffffff then
c6 = c6 + 1
c5 = c5 - 0x1000000
end
if c6 > 0xffffff then
c7 = c7 + 1
c6 = c6 - 0x1000000
end
if c7 > 0xffffff then
c8 = c8 + 1
c7 = c7 - 0x1000000
end
if c8 > 0xffffff then
c9 = c9 + 1
c8 = c8 - 0x1000000
end
if c9 > 0xffffff then
c10 = c10 + 1
c9 = c9 - 0x1000000
end
if c10 > 0xffffff then
c11 = c11 + 1
c10 = c10 - 0x1000000
end
if c11 > 0xffffff then
c12 = c12 + 1
c11 = c11 - 0x1000000
end
if c12 > 0xffffff then
c13 = c13 + 1
c12 = c12 - 0x1000000
end
if c13 > 0xffffff then
c14 = c14 + 1
c13 = c13 - 0x1000000
end
return {c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14}
end
local function mult(a, b, half_multiply)
local a1, a2, a3, a4, a5, a6, a7 = unpack(a)
local b1, b2, b3, b4, b5, b6, b7 = unpack(b)
local c1 = a1 * b1
local c2 = a1 * b2 + a2 * b1
local c3 = a1 * b3 + a2 * b2 + a3 * b1
local c4 = a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1
local c5 = a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1
local c6 = a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1
local c7 = a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2
+ a7 * b1
local c8, c9, c10, c11, c12, c13, c14
if not half_multiply then
c8 = a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2
c9 = a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3
c10 = a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4
c11 = a5 * b7 + a6 * b6 + a7 * b5
c12 = a6 * b7 + a7 * b6
c13 = a7 * b7
c14 = 0
else
c8 = 0
end
local temp
temp = c1
c1 = c1 % 0x1000000
c2 = c2 + (temp - c1) / 0x1000000
temp = c2
c2 = c2 % 0x1000000
c3 = c3 + (temp - c2) / 0x1000000
temp = c3
c3 = c3 % 0x1000000
c4 = c4 + (temp - c3) / 0x1000000
temp = c4
c4 = c4 % 0x1000000
c5 = c5 + (temp - c4) / 0x1000000
temp = c5
c5 = c5 % 0x1000000
c6 = c6 + (temp - c5) / 0x1000000
temp = c6
c6 = c6 % 0x1000000
c7 = c7 + (temp - c6) / 0x1000000
temp = c7
c7 = c7 % 0x1000000
if not half_multiply then
c8 = c8 + (temp - c7) / 0x1000000
temp = c8
c8 = c8 % 0x1000000
c9 = c9 + (temp - c8) / 0x1000000
temp = c9
c9 = c9 % 0x1000000
c10 = c10 + (temp - c9) / 0x1000000
temp = c10
c10 = c10 % 0x1000000
c11 = c11 + (temp - c10) / 0x1000000
temp = c11
c11 = c11 % 0x1000000
c12 = c12 + (temp - c11) / 0x1000000
temp = c12
c12 = c12 % 0x1000000
c13 = c13 + (temp - c12) / 0x1000000
temp = c13
c13 = c13 % 0x1000000
c14 = c14 + (temp - c13) / 0x1000000
end
return {c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14}
end
local function square(a)
-- returns a 336-bit integer (14 words)
local a1, a2, a3, a4, a5, a6, a7 = unpack(a)
local c1 = a1 * a1
local c2 = a1 * a2 * 2
local c3 = a1 * a3 * 2 + a2 * a2
local c4 = a1 * a4 * 2 + a2 * a3 * 2
local c5 = a1 * a5 * 2 + a2 * a4 * 2 + a3 * a3
local c6 = a1 * a6 * 2 + a2 * a5 * 2 + a3 * a4 * 2
local c7 = a1 * a7 * 2 + a2 * a6 * 2 + a3 * a5 * 2 + a4 * a4
local c8 = a2 * a7 * 2 + a3 * a6 * 2 + a4 * a5 * 2
local c9 = a3 * a7 * 2 + a4 * a6 * 2 + a5 * a5
local c10 = a4 * a7 * 2 + a5 * a6 * 2
local c11 = a5 * a7 * 2 + a6 * a6
local c12 = a6 * a7 * 2
local c13 = a7 * a7
local c14 = 0
local temp
temp = c1
c1 = c1 % 0x1000000
c2 = c2 + (temp - c1) / 0x1000000
temp = c2
c2 = c2 % 0x1000000
c3 = c3 + (temp - c2) / 0x1000000
temp = c3
c3 = c3 % 0x1000000
c4 = c4 + (temp - c3) / 0x1000000
temp = c4
c4 = c4 % 0x1000000
c5 = c5 + (temp - c4) / 0x1000000
temp = c5
c5 = c5 % 0x1000000
c6 = c6 + (temp - c5) / 0x1000000
temp = c6
c6 = c6 % 0x1000000
c7 = c7 + (temp - c6) / 0x1000000
temp = c7
c7 = c7 % 0x1000000
c8 = c8 + (temp - c7) / 0x1000000
temp = c8
c8 = c8 % 0x1000000
c9 = c9 + (temp - c8) / 0x1000000
temp = c9
c9 = c9 % 0x1000000
c10 = c10 + (temp - c9) / 0x1000000
temp = c10
c10 = c10 % 0x1000000
c11 = c11 + (temp - c10) / 0x1000000
temp = c11
c11 = c11 % 0x1000000
c12 = c12 + (temp - c11) / 0x1000000
temp = c12
c12 = c12 % 0x1000000
c13 = c13 + (temp - c12) / 0x1000000
temp = c13
c13 = c13 % 0x1000000
c14 = c14 + (temp - c13) / 0x1000000
return {c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14}
end
local function encodeInt(a)
local enc = {}
for i = 1, 7 do
local word = a[i]
for j = 1, 3 do
enc[#enc + 1] = word % 256
word = math.floor(word / 256)
end
end
return enc
end
local function decodeInt(enc)
local a = {}
local encCopy = {}
for i = 1, 21 do
local byte = enc[i]
assert(type(byte) == "number", "integer decoding failure")
assert(byte >= 0 and byte <= 255, "integer decoding failure")
assert(byte % 1 == 0, "integer decoding failure")
encCopy[i] = byte
end
for i = 1, 21, 3 do
local word = 0
for j = 2, 0, -1 do
word = word * 256
word = word + encCopy[i + j]
end
a[#a + 1] = word
end
return a
end
local function mods(d, w)
local result = d[1] % 2^w
if result >= 2^(w - 1) then
result = result - 2^w
end
return result
end
-- Represents a 168-bit number as the (2^w)-ary Non-Adjacent Form
local function NAF(d, w)
local t = {}
local d = {unpack(d)}
for i = 1, 168 do
if d[1] % 2 == 1 then
t[#t + 1] = mods(d, w)
d = sub(d, {t[#t], 0, 0, 0, 0, 0, 0})
else
t[#t + 1] = 0
end
d = rShift(d)
end
return t
end
return {
isEqual = isEqual,
compare = compare,
add = add,
sub = sub,
addDouble = addDouble,
mult = mult,
square = square,
encodeInt = encodeInt,
decodeInt = decodeInt,
NAF = NAF
}
end)()
-- Arithmetic on the finite field of integers modulo p
-- Where p is the finite field modulus
local modp = (function()
local add = arith.add
local sub = arith.sub
local addDouble = arith.addDouble
local mult = arith.mult
local square = arith.square
local p = {3, 0, 0, 0, 0, 0, 15761408}
-- We're using the Montgomery Reduction for fast modular multiplication.
-- https://en.wikipedia.org/wiki/Montgomery_modular_multiplication
-- r = 2^168
-- p * pInverse = -1 (mod r)
-- r2 = r * r (mod p)
local pInverse = {5592405, 5592405, 5592405, 5592405, 5592405, 5592405, 14800213}
local r2 = {13533400, 837116, 6278376, 13533388, 837116, 6278376, 7504076}
local function multByP(a)
local a1, a2, a3, a4, a5, a6, a7 = unpack(a)
local c1 = a1 * 3
local c2 = a2 * 3
local c3 = a3 * 3
local c4 = a4 * 3
local c5 = a5 * 3
local c6 = a6 * 3
local c7 = a1 * 15761408
c7 = c7 + a7 * 3
local c8 = a2 * 15761408
local c9 = a3 * 15761408
local c10 = a4 * 15761408
local c11 = a5 * 15761408
local c12 = a6 * 15761408
local c13 = a7 * 15761408
local c14 = 0
local temp
temp = c1 / 0x1000000
c2 = c2 + (temp - temp % 1)
c1 = c1 % 0x1000000
temp = c2 / 0x1000000
c3 = c3 + (temp - temp % 1)
c2 = c2 % 0x1000000
temp = c3 / 0x1000000
c4 = c4 + (temp - temp % 1)
c3 = c3 % 0x1000000
temp = c4 / 0x1000000
c5 = c5 + (temp - temp % 1)
c4 = c4 % 0x1000000
temp = c5 / 0x1000000
c6 = c6 + (temp - temp % 1)
c5 = c5 % 0x1000000
temp = c6 / 0x1000000
c7 = c7 + (temp - temp % 1)
c6 = c6 % 0x1000000
temp = c7 / 0x1000000
c8 = c8 + (temp - temp % 1)
c7 = c7 % 0x1000000
temp = c8 / 0x1000000
c9 = c9 + (temp - temp % 1)
c8 = c8 % 0x1000000
temp = c9 / 0x1000000
c10 = c10 + (temp - temp % 1)
c9 = c9 % 0x1000000
temp = c10 / 0x1000000
c11 = c11 + (temp - temp % 1)
c10 = c10 % 0x1000000
temp = c11 / 0x1000000
c12 = c12 + (temp - temp % 1)
c11 = c11 % 0x1000000
temp = c12 / 0x1000000
c13 = c13 + (temp - temp % 1)
c12 = c12 % 0x1000000
temp = c13 / 0x1000000
c14 = c14 + (temp - temp % 1)
c13 = c13 % 0x1000000
return {c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14}
end
-- Reduces a number from [0, 2p - 1] to [0, p - 1]
local function reduceModP(a)
-- a < p
if a[7] < 15761408 or a[7] == 15761408 and a[1] < 3 then
return {unpack(a)}
end
-- a > p
local c1 = a[1]
local c2 = a[2]
local c3 = a[3]
local c4 = a[4]
local c5 = a[5]
local c6 = a[6]
local c7 = a[7]
c1 = c1 - 3
c7 = c7 - 15761408
if c1 < 0 then
c2 = c2 - 1
c1 = c1 + 0x1000000
end
if c2 < 0 then
c3 = c3 - 1
c2 = c2 + 0x1000000
end
if c3 < 0 then
c4 = c4 - 1
c3 = c3 + 0x1000000
end
if c4 < 0 then
c5 = c5 - 1
c4 = c4 + 0x1000000
end
if c5 < 0 then
c6 = c6 - 1
c5 = c5 + 0x1000000
end
if c6 < 0 then
c7 = c7 - 1
c6 = c6 + 0x1000000
end
return {c1, c2, c3, c4, c5, c6, c7}
end
local function addModP(a, b)
return reduceModP(add(a, b))
end
local function subModP(a, b)
local result = sub(a, b)
if result[7] < 0 then
result = add(result, p)
end
return result
end
-- Montgomery REDC algorithn
-- Reduces a number from [0, p^2 - 1] to [0, p - 1]
local function REDC(T)
local m = mult(T, pInverse, true)
local t = {unpack(addDouble(T, multByP(m)), 8, 14)}
return reduceModP(t)
end
local function multModP(a, b)
-- Only works with a, b in Montgomery form
return REDC(mult(a, b))
end
local function squareModP(a)
-- Only works with a in Montgomery form
return REDC(square(a))
end
local function montgomeryModP(a)
return multModP(a, r2)
end
local function inverseMontgomeryModP(a)
local a = {unpack(a)}
for i = 8, 14 do
a[i] = 0
end
return REDC(a)
end
local ONE = montgomeryModP({1, 0, 0, 0, 0, 0, 0})
local function expModP(base, exponentBinary)
local base = {unpack(base)}
local result = {unpack(ONE)}
for i = 1, 168 do
if exponentBinary[i] == 1 then
result = multModP(result, base)
end
base = squareModP(base)
end
return result
end
return {
addModP = addModP,
subModP = subModP,
multModP = multModP,
squareModP = squareModP,
montgomeryModP = montgomeryModP,
inverseMontgomeryModP = inverseMontgomeryModP,
expModP = expModP
}
end)()
-- Arithmetic on the Finite Field of Integers modulo q
-- Where q is the generator's subgroup order.
local modq = (function()
local isEqual = arith.isEqual
local compare = arith.compare
local add = arith.add
local sub = arith.sub
local addDouble = arith.addDouble
local mult = arith.mult
local square = arith.square
local encodeInt = arith.encodeInt
local decodeInt = arith.decodeInt
local modQMT
local q = {9622359, 6699217, 13940450, 16775734, 16777215, 16777215, 3940351}
local qMinusTwoBinary = {1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1}
-- We're using the Montgomery Reduction for fast modular multiplication.
-- https://en.wikipedia.org/wiki/Montgomery_modular_multiplication
-- r = 2^168
-- q * qInverse = -1 (mod r)
-- r2 = r * r (mod q)
local qInverse = {15218585, 5740955, 3271338, 9903997, 9067368, 7173545, 6988392}
local r2 = {1336213, 11071705, 9716828, 11083885, 9188643, 1494868, 3306114}
-- Reduces a number from [0, 2q - 1] to [0, q - 1]
local function reduceModQ(a)
local result = {unpack(a)}
if compare(result, q) >= 0 then
result = sub(result, q)
end
return setmetatable(result, modQMT)
end
local function addModQ(a, b)
return reduceModQ(add(a, b))
end
local function subModQ(a, b)
local result = sub(a, b)
if result[7] < 0 then
result = add(result, q)
end
return setmetatable(result, modQMT)
end
-- Montgomery REDC algorithn
-- Reduces a number from [0, q^2 - 1] to [0, q - 1]
local function REDC(T)
local m = {unpack(mult({unpack(T, 1, 7)}, qInverse, true), 1, 7)}
local t = {unpack(addDouble(T, mult(m, q)), 8, 14)}
return reduceModQ(t)
end
local function multModQ(a, b)
-- Only works with a, b in Montgomery form
return REDC(mult(a, b))
end
local function squareModQ(a)
-- Only works with a in Montgomery form
return REDC(square(a))
end
local function montgomeryModQ(a)
return multModQ(a, r2)
end
local function inverseMontgomeryModQ(a)
local a = {unpack(a)}
for i = 8, 14 do
a[i] = 0
end
return REDC(a)
end
local ONE = montgomeryModQ({1, 0, 0, 0, 0, 0, 0})
local function expModQ(base, exponentBinary)
local base = {unpack(base)}
local result = {unpack(ONE)}
for i = 1, 168 do
if exponentBinary[i] == 1 then
result = multModQ(result, base)
end
base = squareModQ(base)
end
return result
end
local function intExpModQ(base, exponent)
local base = {unpack(base)}
local result = setmetatable({unpack(ONE)}, modQMT)
if exponent < 0 then
base = expModQ(base, qMinusTwoBinary)
exponent = -exponent
end
while exponent > 0 do
if exponent % 2 == 1 then
result = multModQ(result, base)
end
base = squareModQ(base)
exponent = exponent / 2
exponent = exponent - exponent % 1
end
return result
end
local function encodeModQ(a)
local result = encodeInt(a)
return setmetatable(result, byteTableMT)
end
local function decodeModQ(s)
s = type(s) == "table" and {unpack(s, 1, 21)} or {tostring(s):byte(1, 21)}
local result = decodeInt(s)
result[7] = result[7] % q[7]
return setmetatable(result, modQMT)
end
local function randomModQ()
while true do
local s = {unpack(random.random(), 1, 21)}
local result = decodeInt(s)
if result[7] < q[7] then
return setmetatable(result, modQMT)
end
end
end
local function hashModQ(data)
return decodeModQ(sha256.digest(data))
end
modQMT = {
__index = {
encode = function(self)
return encodeModQ(self)
end
},
__tostring = function(self)
return self:encode():toHex()
end,
__add = function(self, other)
if type(self) == "number" then
return other + self
end
if type(other) == "number" then
assert(other < 2^24, "number operand too big")
other = montgomeryModQ({other, 0, 0, 0, 0, 0, 0})
end
return addModQ(self, other)
end,
__sub = function(a, b)
if type(a) == "number" then
assert(a < 2^24, "number operand too big")
a = montgomeryModQ({a, 0, 0, 0, 0, 0, 0})
end
if type(b) == "number" then
assert(b < 2^24, "number operand too big")
b = montgomeryModQ({b, 0, 0, 0, 0, 0, 0})
end
return subModQ(a, b)
end,
__unm = function(self)
return subModQ(q, self)
end,
__eq = function(self, other)
return isEqual(self, other)
end,
__mul = function(self, other)
if type(self) == "number" then
return other * self
end
-- EC point
-- Use the point's metatable to handle multiplication
if type(other) == "table" and type(other[1]) == "table" then
return other * self
end
if type(other) == "number" then
assert(other < 2^24, "number operand too big")
other = montgomeryModQ({other, 0, 0, 0, 0, 0, 0})
end
return multModQ(self, other)
end,
__div = function(a, b)
if type(a) == "number" then
assert(a < 2^24, "number operand too big")
a = montgomeryModQ({a, 0, 0, 0, 0, 0, 0})
end
if type(b) == "number" then
assert(b < 2^24, "number operand too big")
b = montgomeryModQ({b, 0, 0, 0, 0, 0, 0})
end
local bInv = expModQ(b, qMinusTwoBinary)
return multModQ(a, bInv)
end,
__pow = function(self, other)
return intExpModQ(self, other)
end
}
return {
hashModQ = hashModQ,
randomModQ = randomModQ,
decodeModQ = decodeModQ,
inverseMontgomeryModQ = inverseMontgomeryModQ
}
end)()
-- Elliptic curve arithmetic
local curve = (function()
---- About the Curve Itself
-- Field Size: 168 bits
-- Field Modulus (p): 481 * 2^159 + 3
-- Equation: x^2 + y^2 = 1 + 122 * x^2 * y^2
-- Parameters: Edwards Curve with d = 122
-- Curve Order (n): 351491143778082151827986174289773107581916088585564
-- Cofactor (h): 4
-- Generator Order (q): 87872785944520537956996543572443276895479022146391
---- About the Curve's Security
-- Current best attack security: 81.777 bits (Small Subgroup + Rho)
-- Rho Security: log2(0.884 * sqrt(q)) = 82.777 bits
-- Transfer Security? Yes: p ~= q; k > 20
-- Field Discriminant Security? Yes:
-- t = 27978492958645335688000168
-- s = 10
-- |D| = 6231685068753619775430107799412237267322159383147 > 2^100
-- Rigidity? No, not at all.
-- XZ/YZ Ladder Security? No: Single coordinate ladders are insecure.
-- Small Subgroup Security? No.
-- Invalid Curve Security? Yes: Points are checked before every operation.
-- Invalid Curve Twist Security? No: Don't use single coordinate ladders.
-- Completeness? Yes: The curve is complete.
-- Indistinguishability? Yes (Elligator 2), but not implemented.
local isEqual = arith.isEqual
local NAF = arith.NAF
local encodeInt = arith.encodeInt
local decodeInt = arith.decodeInt
local multModP = modp.multModP
local squareModP = modp.squareModP
local addModP = modp.addModP
local subModP = modp.subModP
local montgomeryModP = modp.montgomeryModP
local expModP = modp.expModP
local inverseMontgomeryModQ = modq.inverseMontgomeryModQ
local pointMT
local ZERO = {0, 0, 0, 0, 0, 0, 0}
local ONE = montgomeryModP({1, 0, 0, 0, 0, 0, 0})
-- Curve Parameters
local d = montgomeryModP({122, 0, 0, 0, 0, 0, 0})
local p = {3, 0, 0, 0, 0, 0, 15761408}
local pMinusTwoBinary = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1}
local pMinusThreeOverFourBinary = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1}
local G = {
{6636044, 10381432, 15741790, 2914241, 5785600, 264923, 4550291},
{13512827, 8449886, 5647959, 1135556, 5489843, 7177356, 8002203},
{unpack(ONE)}
}
local O = {
{unpack(ZERO)},
{unpack(ONE)},
{unpack(ONE)}
}
-- Projective Coordinates for Edwards curves for point addition/doubling.
-- Points are represented as: (X:Y:Z) where x = X/Z and y = Y/Z
-- The identity element is represented by (0:1:1)
-- Point operation formulas are available on the EFD:
-- https://www.hyperelliptic.org/EFD/g1p/auto-edwards-projective.html
local function pointDouble(P1)
-- 3M + 4S
local X1, Y1, Z1 = unpack(P1)
local b = addModP(X1, Y1)
local B = squareModP(b)
local C = squareModP(X1)
local D = squareModP(Y1)
local E = addModP(C, D)
local H = squareModP(Z1)
local J = subModP(E, addModP(H, H))
local X3 = multModP(subModP(B, E), J)
local Y3 = multModP(E, subModP(C, D))
local Z3 = multModP(E, J)
local P3 = {X3, Y3, Z3}
return setmetatable(P3, pointMT)
end
local function pointAdd(P1, P2)
-- 10M + 1S
local X1, Y1, Z1 = unpack(P1)
local X2, Y2, Z2 = unpack(P2)
local A = multModP(Z1, Z2)
local B = squareModP(A)
local C = multModP(X1, X2)
local D = multModP(Y1, Y2)
local E = multModP(d, multModP(C, D))
local F = subModP(B, E)
local G = addModP(B, E)
local X3 = multModP(A, multModP(F, subModP(multModP(addModP(X1, Y1), addModP(X2, Y2)), addModP(C, D))))
local Y3 = multModP(A, multModP(G, subModP(D, C)))
local Z3 = multModP(F, G)
local P3 = {X3, Y3, Z3}
return setmetatable(P3, pointMT)
end
local function pointNeg(P1)
local X1, Y1, Z1 = unpack(P1)
local X3 = subModP(ZERO, X1)
local Y3 = {unpack(Y1)}
local Z3 = {unpack(Z1)}
local P3 = {X3, Y3, Z3}
return setmetatable(P3, pointMT)
end
local function pointSub(P1, P2)
return pointAdd(P1, pointNeg(P2))
end
-- Converts (X:Y:Z) into (X:Y:1) = (x:y:1)
local function pointScale(P1)
local X1, Y1, Z1 = unpack(P1)
local A = expModP(Z1, pMinusTwoBinary)
local X3 = multModP(X1, A)
local Y3 = multModP(Y1, A)
local Z3 = {unpack(ONE)}
local P3 = {X3, Y3, Z3}
return setmetatable(P3, pointMT)
end
local function pointIsEqual(P1, P2)
local X1, Y1, Z1 = unpack(P1)
local X2, Y2, Z2 = unpack(P2)
local A1 = multModP(X1, Z2)
local B1 = multModP(Y1, Z2)
local A2 = multModP(X2, Z1)
local B2 = multModP(Y2, Z1)
return isEqual(A1, A2) and isEqual(B1, B2)
end
-- Checks if a projective point satisfies the curve equation
local function pointIsOnCurve(P1)
local X1, Y1, Z1 = unpack(P1)
local X12 = squareModP(X1)
local Y12 = squareModP(Y1)
local Z12 = squareModP(Z1)
local Z14 = squareModP(Z12)
local a = addModP(X12, Y12)
a = multModP(a, Z12)
local b = multModP(d, multModP(X12, Y12))
b = addModP(Z14, b)
return isEqual(a, b)
end
local function pointIsInf(P1)
return isEqual(P1[1], ZERO)
end
-- W-ary Non-Adjacent Form (wNAF) method for scalar multiplication:
-- https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#w-ary_non-adjacent_form_(wNAF)_method
local function scalarMult(multiplier, P1)
-- w = 5
local naf = NAF(multiplier, 5)
local PTable = {P1}
local P2 = pointDouble(P1)
local Q = {{unpack(ZERO)}, {unpack(ONE)}, {unpack(ONE)}}
for i = 3, 31, 2 do
PTable[i] = pointAdd(PTable[i - 2], P2)
end
for i = #naf, 1, -1 do
Q = pointDouble(Q)
if naf[i] > 0 then
Q = pointAdd(Q, PTable[naf[i]])
elseif naf[i] < 0 then
Q = pointSub(Q, PTable[-naf[i]])
end
end
return setmetatable(Q, pointMT)
end
-- Lookup table 4-ary NAF method for scalar multiplication by G.
-- Precomputations for the regular NAF method are done before the multiplication.
local GTable = {G}
for i = 2, 168 do
GTable[i] = pointDouble(GTable[i - 1])
end
local function scalarMultG(multiplier)
local naf = NAF(multiplier, 2)
local Q = {{unpack(ZERO)}, {unpack(ONE)}, {unpack(ONE)}}
for i = 1, 168 do
if naf[i] == 1 then
Q = pointAdd(Q, GTable[i])
elseif naf[i] == -1 then
Q = pointSub(Q, GTable[i])
end
end
return setmetatable(Q, pointMT)
end
-- Point compression and encoding.
-- Compresses curve points to 22 bytes.
local function pointEncode(P1)
P1 = pointScale(P1)
local result = {}
local x, y = unpack(P1)
-- Encode y
result = encodeInt(y)
-- Encode one bit from x
result[22] = x[1] % 2
return setmetatable(result, byteTableMT)
end
local function pointDecode(enc)
enc = type(enc) == "table" and {unpack(enc, 1, 22)} or {tostring(enc):byte(1, 22)}
-- Decode y
local y = decodeInt(enc)
y[7] = y[7] % p[7]
-- Find {x, -x} using curve equation
local y2 = squareModP(y)
local u = subModP(y2, ONE)
local v = subModP(multModP(d, y2), ONE)
local u2 = squareModP(u)
local u3 = multModP(u, u2)
local u5 = multModP(u3, u2)
local v3 = multModP(v, squareModP(v))
local w = multModP(u5, v3)
local x = multModP(u3, multModP(v, expModP(w, pMinusThreeOverFourBinary)))
-- Use enc[22] to find x from {x, -x}
if x[1] % 2 ~= enc[22] then
x = subModP(ZERO, x)
end
local P3 = {x, y, {unpack(ONE)}}
return setmetatable(P3, pointMT)
end
pointMT = {
__index = {
isOnCurve = function(self)
return pointIsOnCurve(self)
end,
isInf = function(self)
return self:isOnCurve() and pointIsInf(self)
end,
encode = function(self)
return pointEncode(self)
end
},
__tostring = function(self)
return self:encode():toHex()
end,
__add = function(P1, P2)
assert(P1:isOnCurve(), "invalid point")
assert(P2:isOnCurve(), "invalid point")
return pointAdd(P1, P2)
end,
__sub = function(P1, P2)
assert(P1:isOnCurve(), "invalid point")
assert(P2:isOnCurve(), "invalid point")
return pointSub(P1, P2)
end,
__unm = function(self)
assert(self:isOnCurve(), "invalid point")
return pointNeg(self)
end,
__eq = function(P1, P2)
assert(P1:isOnCurve(), "invalid point")
assert(P2:isOnCurve(), "invalid point")
return pointIsEqual(P1, P2)
end,
__mul = function(P1, s)
if type(P1) == "number" then
return s * P1
end
if type(s) == "number" then
assert(s < 2^24, "number multiplier too big")
s = {s, 0, 0, 0, 0, 0, 0}
else
s = inverseMontgomeryModQ(s)
end
if P1 == G then
return scalarMultG(s)
else
return scalarMult(s, P1)
end
end
}
G = setmetatable(G, pointMT)
O = setmetatable(O, pointMT)
return {
G = G,
O = O,
pointDecode = pointDecode
}
end)()
local function getNonceFromEpoch()
local nonce = {}
local epoch = os.epoch("utc")
for i = 1, 12 do
nonce[#nonce + 1] = epoch % 256
epoch = epoch / 256
epoch = epoch - epoch % 1
end
return nonce
end
local function encrypt(data, key)
local encKey = sha256.hmac("encKey", key)
local macKey = sha256.hmac("macKey", key)
local nonce = getNonceFromEpoch()
local ciphertext = chacha20.crypt(data, encKey, nonce)
local result = nonce
for i = 1, #ciphertext do
result[#result + 1] = ciphertext[i]
end
local mac = sha256.hmac(result, macKey)
for i = 1, #mac do
result[#result + 1] = mac[i]
end
return setmetatable(result, byteTableMT)
end
local function decrypt(data, key)
local data = type(data) == "table" and {unpack(data)} or {tostring(data):byte(1,-1)}
local encKey = sha256.hmac("encKey", key)
local macKey = sha256.hmac("macKey", key)
local mac = sha256.hmac({unpack(data, 1, #data - 32)}, macKey)
local messageMac = {unpack(data, #data - 31)}
assert(mac:isEqual(messageMac), "invalid mac")
local nonce = {unpack(data, 1, 12)}
local ciphertext = {unpack(data, 13, #data - 32)}
local result = chacha20.crypt(ciphertext, encKey, nonce)
return setmetatable(result, byteTableMT)
end
local function keypair(seed)
local x
if seed then
x = modq.hashModQ(seed)
else
x = modq.randomModQ()
end
local Y = curve.G * x
local privateKey = x:encode()
local publicKey = Y:encode()
return privateKey, publicKey
end
local function exchange(privateKey, publicKey)
local x = modq.decodeModQ(privateKey)
local Y = curve.pointDecode(publicKey)
local Z = Y * x
local sharedSecret = sha256.digest(Z:encode())
return sharedSecret
end
local function sign(privateKey, message)
local message = type(message) == "table" and string.char(unpack(message)) or tostring(message)
local privateKey = type(privateKey) == "table" and string.char(unpack(privateKey)) or tostring(privateKey)
local x = modq.decodeModQ(privateKey)
local k = modq.randomModQ()
local R = curve.G * k
local e = modq.hashModQ(message .. tostring(R))
local s = k - x * e
e = e:encode()
s = s:encode()
local result = e
for i = 1, #s do
result[#result + 1] = s[i]
end
return setmetatable(result, byteTableMT)
end
local function verify(publicKey, message, signature)
local message = type(message) == "table" and string.char(unpack(message)) or tostring(message)
local Y = curve.pointDecode(publicKey)
local e = modq.decodeModQ({unpack(signature, 1, #signature / 2)})
local s = modq.decodeModQ({unpack(signature, #signature / 2 + 1)})
local Rv = curve.G * s + Y * e
local ev = modq.hashModQ(message .. tostring(Rv))
return ev == e
end
return {
chacha20 = chacha20,
sha256 = sha256,
random = random,
encrypt = encrypt,
decrypt = decrypt,
keypair = keypair,
exchange = exchange,
sign = sign,
verify = verify
}
@Lupus590
Copy link

@mylesbartlett72
Copy link

Just an idea for the ecc dependency : https://github.com/Lupus590-CC/CC-Random-Code/blob/master/src/dependencyFetcher.lua

That would really want to have some kind of system to verify the integrity of the fetched dependency (even by just having an installer stub for a particular version of the dependency that has a hardcoded hash of the ecc dependency file as well as the code to verify it), since it is a security-critical dependency (as well as the fact that any code that is executed should ideally be verified first)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment