-- Elliptic Curve Cryptography in Computercraft -- Why do I have this *and* ecc.lua? That one actually implements a different curve so is incompatible with the out-of-CC signature generation in use. ---- 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/) require "urandom" 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 = require "sha256".digest -- 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 = os.urandom(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(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 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(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 { keypair = keypair, exchange = exchange, sign = sign, verify = verify }