diff --git a/ccpi.lua b/ccpi.lua index 1de5e85..9f66d95 100644 --- a/ccpi.lua +++ b/ccpi.lua @@ -62,7 +62,7 @@ local function draw(img, ox, oy, monitor) t.setPaletteColor(colors_list[i], img.palette[i]) end - t.setTextScale(image.scale) + t.setTextScale(img.scale) for y = 1, img.h do t.setCursorPos(ox, oy + y - 1) diff --git a/dh/dh.lua b/dh/dh.lua new file mode 100644 index 0000000..1c74208 --- /dev/null +++ b/dh/dh.lua @@ -0,0 +1,71 @@ +local ecc = require("ecc") + +string.toHex = function(str) + return str:gsub(".", function(ch) return string.format("%02x", ch:byte()) end) +end +string.fromHex = function(hex) + return hex:gsub("%x%x", function(d) return string.char(tonumber(d, 16)) end) +end + +local keypair = {} +if fs.exists("/.id_dh.json") then + local fp = io.open("/.id_dh.json", "r") + local d = textutils.unserializeJSON(fp:read()) + keypair.sk = string.fromHex(d.secret) + keypair.pk = string.fromHex(d.public) + fp:close() +else + printError("no identity found, generating...") + local sk, pk = ecc.keypair(ecc.random.random()) + io.open("/.id_dh.json", "w"):write(textutils.serializeJSON({ + secret = string.toHex(sk), + public = string.toHex(pk) + })):close() + keypair.sk = string.char(table.unpack(sk)) + keypair.pk = string.char(table.unpack(pk)) +end + +print("pubkey: "..string.toHex(keypair.pk)) + +local running = true + +local known_hosts = {} + +parallel.waitForAll(function() while running do -- dh:discover sender + rednet.broadcast(keypair.pk, "dh:discover") + os.sleep(10) +end end, +function() while running do -- dh:discover handler + local id, pk, proto = rednet.receive("dh:discover") + if proto == "dh:discover" then + print("DH discover from "..id.." with key "..pk) + local nonce = string.toHex(ecc.random.random()) + local key = ecc.exchange(keypair.sk, pk) + known_hosts[id] = { + id = id, + pk = pk, + sk = key, + verified = false, + nonce = nonce, + t = os.clock() + } + known_hosts[pk] = known_hosts[id] + rednet.send(id, { + pk = keypair.pk, + msg = ecc.encrypt(nonce, key) + }, "dh:pair") + end +end end, +function() while running do -- dh:pair handler + local id, msg, proto = rednet.receive("dh:pair") + if proto == "dh:pair" then + local key = ecc.exchange(keypair.sk, msg.pk) + known_hosts[id] = { + id = id, + pk = msg.pk, + sk = key, + verified = false, + nonce = ecc.decrypt(msg.msg, msg.msg) + } + end +end end) diff --git a/dh/ecc.lua b/dh/ecc.lua new file mode 100644 index 0000000..1677e16 --- /dev/null +++ b/dh/ecc.lua @@ -0,0 +1,1764 @@ +-- http://www.computercraft.info/forums2/index.php?/topic/29803-elliptic-curve-cryptography/ +-- Elliptic Curve Cryptography in Computercraft + +---- Update (Jun 7 2023) +-- Fix string inputs not working on signatures +-- Switch internal byte arrays to strings on most places +-- Improve entropy gathering by using counting +-- Other general improvements to syntax +---- Update (Jun 4 2021) +-- Fix compatibility with CraftOS-PC +---- 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 function mapToStr(t) + if type(t) == "table" then + return string.char(unpack(t)) + else + return tostring(t) + end +end + +local byteTableMT = { + __tostring = mapToStr, + __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 + } +} + +local function strToByteArr(s) + return setmetatable({s:byte(1, -1)}, byteTableMT) +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 = preprocess(strToByteArr(mapToStr(data or ""))) + 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) + data = strToByteArr(mapToStr(data)) + key = mapToStr(key) + + local blocksize = 64 + + key = #key > blocksize and digest(key) or strToByteArr(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) + salt = strToByteArr(mapToStr(salt)) + local hashlen = 32 + iter = tonumber(iter) or 1000 + dklen = tonumber(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, mapToStr(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) + data = strToByteArr(mapToStr(data)) + key = strToByteArr(mapToStr(key)) + nonce = strToByteArr(mapToStr(nonce)) + 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") + 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 + + -- Add context. + 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("|") + feed(tostring({})) + feed(tostring({})) + digest() + feed(tostring(os.epoch("utc"))) + digest() + + -- Add entropy by counting. + local countTable = {} + local inner = "function()return{" .. ("e'utc',"):rep(256) .. "}end" + local countf = assert(load("local e=os.epoch return " .. inner))() + for i = 1, 300 do + while true do + local t = countf() + local t1 = t[1] + if t1 ~= t[256] then + for j = 1, 256 do + if t1 ~= t[j] then + countTable[i] = j - 1 + break + end + end + break + end + end + end + + feed(mapToStr(countTable)) + 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(mapToStr(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 _ = 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) + 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) + 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) + local result = decodeInt(strToByteArr(mapToStr(s):sub(1, 21))) + 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 = strToByteArr(mapToStr(enc):sub(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 _ = 1, 12 do + nonce[#nonce + 1] = epoch % 256 + epoch = epoch / 256 + epoch = epoch - epoch % 1 + end + + return nonce +end + +local function encrypt(data, key) + key = mapToStr(key) + local encKey = sha256.hmac("encKey", key) + local macKey = sha256.hmac("macKey", key) + local nonce = getNonceFromEpoch() + local ciphertext = chacha20.crypt(mapToStr(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) + data = mapToStr(data) + key = mapToStr(key) + local encKey = sha256.hmac("encKey", key) + local macKey = sha256.hmac("macKey", key) + local mac = sha256.hmac(data:sub(1, -33), macKey) + assert(mac:isEqual(strToByteArr(data:sub(-32))), "invalid mac") + local result = chacha20.crypt(data:sub(13, -33), encKey, data:sub(1, 12)) + + return setmetatable(result, byteTableMT) +end + +local function keypair(seed) + local x + if seed then + x = modq.hashModQ(mapToStr(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(mapToStr(privateKey)) + local Y = curve.pointDecode(mapToStr(publicKey)) + + local Z = Y * x + + local sharedSecret = sha256.digest(Z:encode()) + + return sharedSecret +end + +local function sign(privateKey, message) + local x = modq.decodeModQ(mapToStr(privateKey)) + local k = modq.randomModQ() + local R = curve.G * k + local e = modq.hashModQ(mapToStr(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) + signature = mapToStr(signature) + local Y = curve.pointDecode(mapToStr(publicKey)) + local e = modq.decodeModQ(signature:sub(1, 21)) + local s = modq.decodeModQ(signature:sub(22)) + local Rv = curve.G * s + Y * e + local ev = modq.hashModQ(mapToStr(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 +}