cc-stuff/dh/ecc.lua

1765 lines
53 KiB
Lua

-- 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
}