potatOS/src/lib/ecc-168.lua

1311 lines
37 KiB
Lua

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