Give up on masking for now
X25519c can be attacked by replying several times with invalid data. This is hard to defend against in the API level without denying service and using some hard-to-understand semantics. Masked primitives are gone for now, some countermeasures have been moved into their respective "regular" impls. I don't think that it's worth it to care that much about side channels in CC. I haven't seen or managed to mount any practical attacks myself. The further move away from Cobalt will probably make them even harder to mount.
This commit is contained in:
parent
ed8f66070f
commit
54b821c091
245
ed25519.lua
245
ed25519.lua
|
@ -1,15 +1,231 @@
|
|||
--- The Ed25519 digital signature scheme.
|
||||
--
|
||||
-- **Note:** This library is provided for compatibility and provides no side
|
||||
-- channel resistance by itself.
|
||||
--
|
||||
-- @module ed25519
|
||||
--
|
||||
|
||||
local expect = require "cc.expect".expect
|
||||
local fp = require "ccryptolib.internal.fp"
|
||||
local fq = require "ccryptolib.internal.fq"
|
||||
local ed25519 = require "ccryptolib.internal.ed25519"
|
||||
local sha512 = require "ccryptolib.internal.sha512"
|
||||
local random = require "ccryptolib.random"
|
||||
|
||||
local unpack = unpack or table.unpack
|
||||
|
||||
local D = fp.mul(fp.num(-121665), fp.invert(fp.num(121666)))
|
||||
local K = fp.kmul(D, 2)
|
||||
|
||||
local O = {fp.num(0), fp.num(1), fp.num(1), fp.num(0)}
|
||||
local G = nil
|
||||
|
||||
local function double(P1)
|
||||
local P1x, P1y, P1z = unpack(P1)
|
||||
local a = fp.square(P1x)
|
||||
local b = fp.square(P1y)
|
||||
local c = fp.square(P1z)
|
||||
local d = fp.kmul(c, 2)
|
||||
local e = fp.add(a, b)
|
||||
local f = fp.add(P1x, P1y)
|
||||
local g = fp.square(f)
|
||||
local h = fp.sub(g, e)
|
||||
local i = fp.sub(b, a)
|
||||
local j = fp.sub(d, i)
|
||||
local P3x = fp.mul(h, j)
|
||||
local P3y = fp.mul(i, e)
|
||||
local P3z = fp.mul(j, i)
|
||||
local P3t = fp.mul(h, e)
|
||||
return {P3x, P3y, P3z, P3t}
|
||||
end
|
||||
|
||||
local function add(P1, N1)
|
||||
local P1x, P1y, P1z, P1t = unpack(P1)
|
||||
local N1p, N1m, N1z, N1t = unpack(N1)
|
||||
local a = fp.sub(P1y, P1x)
|
||||
local b = fp.mul(a, N1m)
|
||||
local c = fp.add(P1y, P1x)
|
||||
local d = fp.mul(c, N1p)
|
||||
local e = fp.mul(P1t, N1t)
|
||||
local f = fp.mul(P1z, N1z)
|
||||
local g = fp.sub(d, b)
|
||||
local h = fp.sub(f, e)
|
||||
local i = fp.add(f, e)
|
||||
local j = fp.add(d, b)
|
||||
local P3x = fp.mul(g, h)
|
||||
local P3y = fp.mul(i, j)
|
||||
local P3z = fp.mul(h, i)
|
||||
local P3t = fp.mul(g, j)
|
||||
return {P3x, P3y, P3z, P3t}
|
||||
end
|
||||
|
||||
local function sub(P1, N1)
|
||||
local P1x, P1y, P1z, P1t = unpack(P1)
|
||||
local N1p, N1m, N1z, N1t = unpack(N1)
|
||||
local a = fp.sub(P1y, P1x)
|
||||
local b = fp.mul(a, N1p)
|
||||
local c = fp.add(P1y, P1x)
|
||||
local d = fp.mul(c, N1m)
|
||||
local e = fp.mul(P1t, N1t)
|
||||
local f = fp.mul(P1z, N1z)
|
||||
local g = fp.sub(d, b)
|
||||
local h = fp.add(f, e)
|
||||
local i = fp.sub(f, e)
|
||||
local j = fp.add(d, b)
|
||||
local P3x = fp.mul(g, h)
|
||||
local P3y = fp.mul(i, j)
|
||||
local P3z = fp.mul(h, i)
|
||||
local P3t = fp.mul(g, j)
|
||||
return {P3x, P3y, P3z, P3t}
|
||||
end
|
||||
|
||||
local function niels(P1)
|
||||
local P1x, P1y, P1z, P1t = unpack(P1)
|
||||
local N3p = fp.add(P1y, P1x)
|
||||
local N3m = fp.sub(P1y, P1x)
|
||||
local N3z = fp.add(P1z, P1z)
|
||||
local N3t = fp.mul(P1t, K)
|
||||
return {N3p, N3m, N3z, N3t}
|
||||
end
|
||||
|
||||
local function scale(P1)
|
||||
local P1x, P1y, P1z = unpack(P1)
|
||||
local zInv = fp.invert(P1z)
|
||||
local P3x = fp.mul(P1x, zInv)
|
||||
local P3y = fp.mul(P1y, zInv)
|
||||
local P3z = fp.num(1)
|
||||
local P3t = fp.mul(P3x, P3y)
|
||||
return {P3x, P3y, P3z, P3t}
|
||||
end
|
||||
|
||||
local function encode(P1)
|
||||
local P1x, P1y = unpack(P1)
|
||||
local y = fp.encode(P1y)
|
||||
local xBit = fp.canonicalize(P1x)[1] % 2
|
||||
return y:sub(1, -2) .. string.char(y:byte(-1) + xBit * 128)
|
||||
end
|
||||
|
||||
local function decode(str)
|
||||
local P3y = fp.decode(str)
|
||||
local a = fp.square(P3y)
|
||||
local b = fp.sub(a, fp.num(1))
|
||||
local c = fp.mul(a, D)
|
||||
local d = fp.add(c, fp.num(1))
|
||||
local P3x = fp.sqrtDiv(b, d)
|
||||
if not P3x then return nil end
|
||||
local xBit = fp.canonicalize(P3x)[1] % 2
|
||||
if xBit ~= bit32.extract(str:byte(-1), 7) then
|
||||
P3x = fp.neg(P3x)
|
||||
P3x = fp.carry(P3x)
|
||||
end
|
||||
local P3z = fp.num(1)
|
||||
local P3t = fp.mul(P3x, P3y)
|
||||
return {P3x, P3y, P3z, P3t}
|
||||
end
|
||||
|
||||
G = decode("Xfffffffffffffffffffffffffffffff")
|
||||
|
||||
local function signedRadixW(bits, w)
|
||||
-- TODO Find a more elegant way of doing this.
|
||||
local wPow = 2 ^ w
|
||||
local wPowh = wPow / 2
|
||||
local out = {}
|
||||
local acc = 0
|
||||
local mul = 1
|
||||
for i = 1, #bits do
|
||||
acc = acc + bits[i] * mul
|
||||
mul = mul * 2
|
||||
while i == #bits and acc > 0 or mul > wPow do
|
||||
local rem = acc % wPow
|
||||
if rem >= wPowh then rem = rem - wPow end
|
||||
acc = (acc - rem) / wPow
|
||||
mul = mul / wPow
|
||||
out[#out + 1] = rem
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function radixWTable(P, w)
|
||||
local out = {}
|
||||
for i = 1, 255 / w do
|
||||
local row = {niels(P)}
|
||||
for j = 2, 2 ^ w / 2 do
|
||||
P = add(P, row[1])
|
||||
row[j] = niels(P)
|
||||
end
|
||||
out[i] = row
|
||||
P = double(P)
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local G_W = 5
|
||||
local G_TABLE = radixWTable(G, G_W)
|
||||
|
||||
local function WNAF(bits, w)
|
||||
-- TODO Find a more elegant way of doing this.
|
||||
local wPow = 2 ^ w
|
||||
local wPowh = wPow / 2
|
||||
local out = {}
|
||||
local acc = 0
|
||||
local mul = 1
|
||||
for i = 1, #bits do
|
||||
acc = acc + bits[i] * mul
|
||||
mul = mul * 2
|
||||
while i == #bits and acc > 0 or mul > wPow do
|
||||
if acc % 2 == 0 then
|
||||
acc = acc / 2
|
||||
mul = mul / 2
|
||||
out[#out + 1] = 0
|
||||
else
|
||||
local rem = acc % wPow
|
||||
if rem >= wPowh then rem = rem - wPow end
|
||||
acc = acc - rem
|
||||
out[#out + 1] = rem
|
||||
end
|
||||
end
|
||||
end
|
||||
while out[#out] == 0 do out[#out] = nil end
|
||||
return out
|
||||
end
|
||||
|
||||
local function WNAFTable(P, w)
|
||||
local dP = double(P)
|
||||
local out = {niels(P)}
|
||||
for i = 3, 2 ^ w, 2 do
|
||||
out[i] = niels(add(dP, out[i - 2]))
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function mulG(bits)
|
||||
local sw = signedRadixW(bits, G_W)
|
||||
local R = O
|
||||
for i = 1, #sw do
|
||||
local b = sw[i]
|
||||
if b > 0 then
|
||||
R = add(R, G_TABLE[i][b])
|
||||
elseif b < 0 then
|
||||
R = sub(R, G_TABLE[i][-b])
|
||||
end
|
||||
end
|
||||
return R
|
||||
end
|
||||
|
||||
local function mul(P, bits)
|
||||
local naf = WNAF(bits, 5)
|
||||
local tbl = WNAFTable(P, 5)
|
||||
local R = O
|
||||
for i = #naf, 1, -1 do
|
||||
local b = naf[i]
|
||||
if b == 0 then
|
||||
R = double(R)
|
||||
elseif b > 0 then
|
||||
R = add(R, tbl[b])
|
||||
else
|
||||
R = sub(R, tbl[-b])
|
||||
end
|
||||
end
|
||||
return R
|
||||
end
|
||||
|
||||
local mod = {}
|
||||
|
||||
|
@ -25,7 +241,7 @@ function mod.publicKey(sk)
|
|||
local h = sha512.digest(sk)
|
||||
local x = fq.decodeClamped(h:sub(1, 32))
|
||||
|
||||
return ed25519.encode(ed25519.scale(ed25519.mulG(fq.bits(x))))
|
||||
return encode(scale(mulG(fq.bits(x))))
|
||||
end
|
||||
|
||||
--- Signs a message.
|
||||
|
@ -47,15 +263,16 @@ function mod.sign(sk, pk, msg)
|
|||
local x = fq.decodeClamped(h:sub(1, 32))
|
||||
|
||||
-- Commitment.
|
||||
local k = fq.decodeWide(sha512.digest(h:sub(33) .. msg))
|
||||
local r = ed25519.mulG(fq.bits(k))
|
||||
local rStr = ed25519.encode(ed25519.scale(r))
|
||||
local k = fq.decodeWide(random.random(64))
|
||||
local r = mulG(fq.bits(k))
|
||||
local rStr = encode(scale(r))
|
||||
|
||||
-- Challenge.
|
||||
local e = fq.decodeWide(sha512.digest(rStr .. pk .. msg))
|
||||
|
||||
-- Response.
|
||||
local s = fq.add(k, fq.neg(fq.mul(x, e)))
|
||||
local m = fq.decodeWide(random.random(64))
|
||||
local s = fq.add(fq.add(k, fq.neg(fq.mul(fq.add(x, m), e))), fq.mul(m, e))
|
||||
local sStr = fq.encode(s)
|
||||
|
||||
return rStr .. sStr
|
||||
|
@ -75,7 +292,7 @@ function mod.verify(pk, msg, sig)
|
|||
expect(3, sig, "string")
|
||||
assert(#sig == 64, "signature length must be 64")
|
||||
|
||||
local y = ed25519.decode(pk)
|
||||
local y = decode(pk)
|
||||
if not y then return nil end
|
||||
|
||||
local rStr = sig:sub(1, 32)
|
||||
|
@ -83,11 +300,11 @@ function mod.verify(pk, msg, sig)
|
|||
|
||||
local e = fq.decodeWide(sha512.digest(rStr .. pk .. msg))
|
||||
|
||||
local gs = ed25519.mulG(fq.bits(fq.decode(sStr)))
|
||||
local ye = ed25519.mul(y, fq.bits(e))
|
||||
local rv = ed25519.add(gs, ed25519.niels(ye))
|
||||
local gs = mulG(fq.bits(fq.decode(sStr)))
|
||||
local ye = mul(y, fq.bits(e))
|
||||
local rv = add(gs, niels(ye))
|
||||
|
||||
return ed25519.encode(ed25519.scale(rv)) == rStr
|
||||
return encode(scale(rv)) == rStr
|
||||
end
|
||||
|
||||
return mod
|
||||
|
|
53
ed25519c.lua
53
ed25519c.lua
|
@ -1,53 +0,0 @@
|
|||
local expect = require "cc.expect".expect
|
||||
local fq = require "ccryptolib.internal.fq"
|
||||
local sha512 = require "ccryptolib.internal.sha512"
|
||||
local ed25519 = require "ccryptolib.internal.ed25519"
|
||||
local maddq = require "ccryptolib.internal.maddq"
|
||||
local random = require "ccryptolib.random"
|
||||
|
||||
local ORDER = 4
|
||||
|
||||
local mod = {}
|
||||
|
||||
function mod.new(sk)
|
||||
expect(1, sk, "string")
|
||||
assert(#sk == 32, "secret key length must be 32")
|
||||
|
||||
return maddq.new(fq.decodeClamped(sha512.digest(sk):sub(1, 32)), ORDER)
|
||||
end
|
||||
|
||||
function mod.encode(sks)
|
||||
return maddq.encode(sks)
|
||||
end
|
||||
|
||||
function mod.decode(str)
|
||||
expect(1, str, "string")
|
||||
assert(#str == 128, "encoded sks length must be 128")
|
||||
|
||||
return maddq.decode(str)
|
||||
end
|
||||
|
||||
function mod.remask(sks)
|
||||
return maddq.remask(sks)
|
||||
end
|
||||
|
||||
function mod.sign(sks, pk, msg)
|
||||
-- Commitment.
|
||||
local k = fq.decodeWide(random.random(64))
|
||||
local r = ed25519.mulG(fq.bits(k))
|
||||
local rStr = ed25519.encode(ed25519.scale(r))
|
||||
|
||||
-- Challenge.
|
||||
local e = fq.decodeWide(sha512.digest(rStr .. pk .. msg))
|
||||
|
||||
-- Response.
|
||||
-- Reduce secret key using the challenge and an extra mask.
|
||||
local m = fq.decodeWide(random.random(64))
|
||||
local xme = maddq.unwrap(maddq.mul(maddq.add(sks, m), e))
|
||||
local s = fq.add(fq.add(k, fq.neg(xme)), fq.mul(m, e))
|
||||
local sStr = fq.encode(s)
|
||||
|
||||
return rStr .. sStr
|
||||
end
|
||||
|
||||
return mod
|
|
@ -1,229 +0,0 @@
|
|||
local fp = require "ccryptolib.internal.fp"
|
||||
|
||||
local unpack = unpack or table.unpack
|
||||
|
||||
local D = fp.mul(fp.num(-121665), fp.invert(fp.num(121666)))
|
||||
local K = fp.kmul(D, 2)
|
||||
|
||||
local O = {fp.num(0), fp.num(1), fp.num(1), fp.num(0)}
|
||||
local G = nil
|
||||
|
||||
local function double(P1)
|
||||
local P1x, P1y, P1z = unpack(P1)
|
||||
local a = fp.square(P1x)
|
||||
local b = fp.square(P1y)
|
||||
local c = fp.square(P1z)
|
||||
local d = fp.kmul(c, 2)
|
||||
local e = fp.add(a, b)
|
||||
local f = fp.add(P1x, P1y)
|
||||
local g = fp.square(f)
|
||||
local h = fp.sub(g, e)
|
||||
local i = fp.sub(b, a)
|
||||
local j = fp.sub(d, i)
|
||||
local P3x = fp.mul(h, j)
|
||||
local P3y = fp.mul(i, e)
|
||||
local P3z = fp.mul(j, i)
|
||||
local P3t = fp.mul(h, e)
|
||||
return {P3x, P3y, P3z, P3t}
|
||||
end
|
||||
|
||||
local function add(P1, N1)
|
||||
local P1x, P1y, P1z, P1t = unpack(P1)
|
||||
local N1p, N1m, N1z, N1t = unpack(N1)
|
||||
local a = fp.sub(P1y, P1x)
|
||||
local b = fp.mul(a, N1m)
|
||||
local c = fp.add(P1y, P1x)
|
||||
local d = fp.mul(c, N1p)
|
||||
local e = fp.mul(P1t, N1t)
|
||||
local f = fp.mul(P1z, N1z)
|
||||
local g = fp.sub(d, b)
|
||||
local h = fp.sub(f, e)
|
||||
local i = fp.add(f, e)
|
||||
local j = fp.add(d, b)
|
||||
local P3x = fp.mul(g, h)
|
||||
local P3y = fp.mul(i, j)
|
||||
local P3z = fp.mul(h, i)
|
||||
local P3t = fp.mul(g, j)
|
||||
return {P3x, P3y, P3z, P3t}
|
||||
end
|
||||
|
||||
local function sub(P1, N1)
|
||||
local P1x, P1y, P1z, P1t = unpack(P1)
|
||||
local N1p, N1m, N1z, N1t = unpack(N1)
|
||||
local a = fp.sub(P1y, P1x)
|
||||
local b = fp.mul(a, N1p)
|
||||
local c = fp.add(P1y, P1x)
|
||||
local d = fp.mul(c, N1m)
|
||||
local e = fp.mul(P1t, N1t)
|
||||
local f = fp.mul(P1z, N1z)
|
||||
local g = fp.sub(d, b)
|
||||
local h = fp.add(f, e)
|
||||
local i = fp.sub(f, e)
|
||||
local j = fp.add(d, b)
|
||||
local P3x = fp.mul(g, h)
|
||||
local P3y = fp.mul(i, j)
|
||||
local P3z = fp.mul(h, i)
|
||||
local P3t = fp.mul(g, j)
|
||||
return {P3x, P3y, P3z, P3t}
|
||||
end
|
||||
|
||||
local function niels(P1)
|
||||
local P1x, P1y, P1z, P1t = unpack(P1)
|
||||
local N3p = fp.add(P1y, P1x)
|
||||
local N3m = fp.sub(P1y, P1x)
|
||||
local N3z = fp.add(P1z, P1z)
|
||||
local N3t = fp.mul(P1t, K)
|
||||
return {N3p, N3m, N3z, N3t}
|
||||
end
|
||||
|
||||
local function scale(P1)
|
||||
local P1x, P1y, P1z = unpack(P1)
|
||||
local zInv = fp.invert(P1z)
|
||||
local P3x = fp.mul(P1x, zInv)
|
||||
local P3y = fp.mul(P1y, zInv)
|
||||
local P3z = fp.num(1)
|
||||
local P3t = fp.mul(P3x, P3y)
|
||||
return {P3x, P3y, P3z, P3t}
|
||||
end
|
||||
|
||||
local function encode(P1)
|
||||
local P1x, P1y = unpack(P1)
|
||||
local y = fp.encode(P1y)
|
||||
local xBit = fp.canonicalize(P1x)[1] % 2
|
||||
return y:sub(1, -2) .. string.char(y:byte(-1) + xBit * 128)
|
||||
end
|
||||
|
||||
local function decode(str)
|
||||
local P3y = fp.decode(str)
|
||||
local a = fp.square(P3y)
|
||||
local b = fp.sub(a, fp.num(1))
|
||||
local c = fp.mul(a, D)
|
||||
local d = fp.add(c, fp.num(1))
|
||||
local P3x = fp.sqrtDiv(b, d)
|
||||
if not P3x then return nil end
|
||||
local xBit = fp.canonicalize(P3x)[1] % 2
|
||||
if xBit ~= bit32.extract(str:byte(-1), 7) then
|
||||
P3x = fp.neg(P3x)
|
||||
P3x = fp.carry(P3x)
|
||||
end
|
||||
local P3z = fp.num(1)
|
||||
local P3t = fp.mul(P3x, P3y)
|
||||
return {P3x, P3y, P3z, P3t}
|
||||
end
|
||||
|
||||
G = decode("Xfffffffffffffffffffffffffffffff")
|
||||
|
||||
local function signedRadixW(bits, w)
|
||||
-- TODO Find a more elegant way of doing this.
|
||||
local wPow = 2 ^ w
|
||||
local wPowh = wPow / 2
|
||||
local out = {}
|
||||
local acc = 0
|
||||
local mul = 1
|
||||
for i = 1, #bits do
|
||||
acc = acc + bits[i] * mul
|
||||
mul = mul * 2
|
||||
while i == #bits and acc > 0 or mul > wPow do
|
||||
local rem = acc % wPow
|
||||
if rem >= wPowh then rem = rem - wPow end
|
||||
acc = (acc - rem) / wPow
|
||||
mul = mul / wPow
|
||||
out[#out + 1] = rem
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function radixWTable(P, w)
|
||||
local out = {}
|
||||
for i = 1, 255 / w do
|
||||
local row = {niels(P)}
|
||||
for j = 2, 2 ^ w / 2 do
|
||||
P = add(P, row[1])
|
||||
row[j] = niels(P)
|
||||
end
|
||||
out[i] = row
|
||||
P = double(P)
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local G_W = 5
|
||||
local G_TABLE = radixWTable(G, G_W)
|
||||
|
||||
local function WNAF(bits, w)
|
||||
-- TODO Find a more elegant way of doing this.
|
||||
local wPow = 2 ^ w
|
||||
local wPowh = wPow / 2
|
||||
local out = {}
|
||||
local acc = 0
|
||||
local mul = 1
|
||||
for i = 1, #bits do
|
||||
acc = acc + bits[i] * mul
|
||||
mul = mul * 2
|
||||
while i == #bits and acc > 0 or mul > wPow do
|
||||
if acc % 2 == 0 then
|
||||
acc = acc / 2
|
||||
mul = mul / 2
|
||||
out[#out + 1] = 0
|
||||
else
|
||||
local rem = acc % wPow
|
||||
if rem >= wPowh then rem = rem - wPow end
|
||||
acc = acc - rem
|
||||
out[#out + 1] = rem
|
||||
end
|
||||
end
|
||||
end
|
||||
while out[#out] == 0 do out[#out] = nil end
|
||||
return out
|
||||
end
|
||||
|
||||
local function WNAFTable(P, w)
|
||||
local dP = double(P)
|
||||
local out = {niels(P)}
|
||||
for i = 3, 2 ^ w, 2 do
|
||||
out[i] = niels(add(dP, out[i - 2]))
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function mulG(bits)
|
||||
local sw = signedRadixW(bits, G_W)
|
||||
local R = O
|
||||
for i = 1, #sw do
|
||||
local b = sw[i]
|
||||
if b > 0 then
|
||||
R = add(R, G_TABLE[i][b])
|
||||
elseif b < 0 then
|
||||
R = sub(R, G_TABLE[i][-b])
|
||||
end
|
||||
end
|
||||
return R
|
||||
end
|
||||
|
||||
local function mul(P, bits)
|
||||
local naf = WNAF(bits, 5)
|
||||
local tbl = WNAFTable(P, 5)
|
||||
local R = O
|
||||
for i = #naf, 1, -1 do
|
||||
local b = naf[i]
|
||||
if b == 0 then
|
||||
R = double(R)
|
||||
elseif b > 0 then
|
||||
R = add(R, tbl[b])
|
||||
else
|
||||
R = sub(R, tbl[-b])
|
||||
end
|
||||
end
|
||||
return R
|
||||
end
|
||||
|
||||
return {
|
||||
add = add,
|
||||
niels = niels,
|
||||
scale = scale,
|
||||
encode = encode,
|
||||
decode = decode,
|
||||
mulG = mulG,
|
||||
mul = mul,
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
--- Additive arithmetic masking modulo q (unstable, for internal use only).
|
||||
--
|
||||
-- Representing secret scalars in Cobalt is potentially dangerous since the VM
|
||||
-- leaks information through timing. To remedy this, we use _masking_, which is
|
||||
-- a randomized representation of a scalar s as several values {s₁, s₂, s₃, ...}
|
||||
-- such that s₁ + s₂ + s₃ + ... ≡ s (mod q).
|
||||
--
|
||||
-- Using masking, we can perform arithmetic on the masked value without needing
|
||||
-- to render the raw secrets in memory. After these operations, we can unwrap
|
||||
-- the result which, depending on the operations, is harder to measure.
|
||||
--
|
||||
-- @module internal.maddq
|
||||
--
|
||||
|
||||
local fq = require "ccryptolib.internal.fq"
|
||||
local random = require "ccryptolib.random"
|
||||
|
||||
--- Builds a masked scalar from a regular one.
|
||||
--
|
||||
-- @tparam {number...} val The input scalar.
|
||||
-- @tparam number order How many values to use, must be at least 2.
|
||||
-- @treturn {{number...}...} The masked scalar.
|
||||
--
|
||||
local function new(val, order)
|
||||
local out = {}
|
||||
local sum = fq.num(0)
|
||||
for i = 1, order - 1 do
|
||||
out[i] = fq.decodeWide(random.random(64))
|
||||
sum = fq.add(sum, out[i])
|
||||
end
|
||||
|
||||
out[order] = fq.add(val, fq.neg(sum))
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
--- Unwraps a masked scalar, getting the raw unmasked value.
|
||||
--
|
||||
-- @tparam {{number...}...} arr The input masked scalar.
|
||||
-- @treturn {number...} The unmasked scalar.
|
||||
--
|
||||
local function unwrap(arr)
|
||||
local sum = fq.num(0)
|
||||
for i = 1, #arr do sum = fq.add(sum, arr[i]) end
|
||||
return sum
|
||||
end
|
||||
|
||||
--- Encodes a masked scalar into a string.
|
||||
--
|
||||
-- @tparam {{number...}...} arr The input masked scalar.
|
||||
-- @treturn string The encoded value.
|
||||
--
|
||||
local function encode(arr)
|
||||
local out = {}
|
||||
for i = 1, #arr do out[i] = fq.encode(arr[i]) end
|
||||
return table.concat(out)
|
||||
end
|
||||
|
||||
--- Decodes a masked scalar from a string.
|
||||
--
|
||||
-- @tparam string str The encoded scalar. Length must be a multiple of 32.
|
||||
-- @treturn {{number...}...} The decoded value.
|
||||
--
|
||||
local function decode(str)
|
||||
local out = {}
|
||||
for i = 1, #str / 32 do out[i] = fq.decode(str:sub(i * 32 - 31, i * 32)) end
|
||||
return out
|
||||
end
|
||||
|
||||
--- Rerandomizes a masked scalar's representation.
|
||||
--
|
||||
-- @tparam {{number...}...} arr The input masked scalar.
|
||||
-- @treturn {{number...}...} The rerandomized representation of the input.
|
||||
--
|
||||
local function remask(arr)
|
||||
local out = new(fq.num(0), #arr)
|
||||
for i = 1, #arr do out[i] = fq.add(out[i], arr[i]) end
|
||||
return out
|
||||
end
|
||||
|
||||
--- Multiplies a masked scalar by a regular scalar.
|
||||
--
|
||||
-- @tparam {{number...}...} arr The input masked scalar.
|
||||
-- @tparam {number...} k The scalar to multiply by.
|
||||
-- @treturn {{number...}...} The masked product of both values.
|
||||
--
|
||||
local function mul(arr, k)
|
||||
local out = {}
|
||||
for i = 1, #arr do out[i] = fq.mul(arr[i], k) end
|
||||
return out
|
||||
end
|
||||
|
||||
--- Adds a regular scalar to a masked scalar.
|
||||
--
|
||||
-- @tparam {{number...}...} The input masked scalar.
|
||||
-- @tparam {number...} v The scalar to add to.
|
||||
-- @treturn {{number...}...} The masked sum of both values.
|
||||
--
|
||||
local function add(arr, v)
|
||||
local out = {}
|
||||
for i = 1, #arr do out[i] = fq.clone(arr[i]) end
|
||||
out[#arr] = fq.add(out[#arr], v)
|
||||
return out
|
||||
end
|
||||
|
||||
return {
|
||||
new = new,
|
||||
unwrap = unwrap,
|
||||
encode = encode,
|
||||
decode = decode,
|
||||
remask = remask,
|
||||
mul = mul,
|
||||
add = add,
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
local fp = require "ccryptolib.internal.fp"
|
||||
|
||||
local G = {9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
|
||||
local function double(x1, z1)
|
||||
local a = fp.add(x1, z1)
|
||||
local aa = fp.square(a)
|
||||
local b = fp.sub(x1, z1)
|
||||
local bb = fp.square(b)
|
||||
local c = fp.sub(aa, bb)
|
||||
local x3 = fp.mul(aa, bb)
|
||||
local z3 = fp.mul(c, fp.add(bb, fp.kmul(c, 121666)))
|
||||
return x3, z3
|
||||
end
|
||||
|
||||
local function step(dx, x1, z1, x2, z2)
|
||||
local a = fp.add(x1, z1)
|
||||
local aa = fp.square(a)
|
||||
local b = fp.sub(x1, z1)
|
||||
local bb = fp.square(b)
|
||||
local e = fp.sub(aa, bb)
|
||||
local c = fp.add(x2, z2)
|
||||
local d = fp.sub(x2, z2)
|
||||
local da = fp.mul(d, a)
|
||||
local cb = fp.mul(c, b)
|
||||
local x4 = fp.square(fp.add(da, cb))
|
||||
local z4 = fp.mul(dx, fp.square(fp.sub(da, cb)))
|
||||
local x3 = fp.mul(aa, bb)
|
||||
local z3 = fp.mul(e, fp.add(bb, fp.kmul(e, 121666)))
|
||||
return x3, z3, x4, z4
|
||||
end
|
||||
|
||||
return {
|
||||
G = G,
|
||||
double = double,
|
||||
step = step,
|
||||
}
|
46
x25519.lua
46
x25519.lua
|
@ -1,9 +1,37 @@
|
|||
local expect = require "cc.expect".expect
|
||||
local fp = require "ccryptolib.internal.fp"
|
||||
local x25519 = require "ccryptolib.internal.x25519"
|
||||
local random = require "ccryptolib.random"
|
||||
|
||||
local unpack = unpack or table.unpack
|
||||
|
||||
local function double(x1, z1)
|
||||
local a = fp.add(x1, z1)
|
||||
local aa = fp.square(a)
|
||||
local b = fp.sub(x1, z1)
|
||||
local bb = fp.square(b)
|
||||
local c = fp.sub(aa, bb)
|
||||
local x3 = fp.mul(aa, bb)
|
||||
local z3 = fp.mul(c, fp.add(bb, fp.kmul(c, 121666)))
|
||||
return x3, z3
|
||||
end
|
||||
|
||||
local function step(dx, x1, z1, x2, z2)
|
||||
local a = fp.add(x1, z1)
|
||||
local aa = fp.square(a)
|
||||
local b = fp.sub(x1, z1)
|
||||
local bb = fp.square(b)
|
||||
local e = fp.sub(aa, bb)
|
||||
local c = fp.add(x2, z2)
|
||||
local d = fp.sub(x2, z2)
|
||||
local da = fp.mul(d, a)
|
||||
local cb = fp.mul(c, b)
|
||||
local x4 = fp.square(fp.add(da, cb))
|
||||
local z4 = fp.mul(dx, fp.square(fp.sub(da, cb)))
|
||||
local x3 = fp.mul(aa, bb)
|
||||
local z3 = fp.mul(e, fp.add(bb, fp.kmul(e, 121666)))
|
||||
return x3, z3, x4, z4
|
||||
end
|
||||
|
||||
local function bits(str)
|
||||
-- Decode.
|
||||
local bytes = {str:byte(1, 32)}
|
||||
|
@ -26,22 +54,24 @@ local function bits(str)
|
|||
end
|
||||
|
||||
local function ladder8(dx, bits)
|
||||
local x1 = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
local z1 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
local x2, z2 = dx, x1
|
||||
local x1 = fp.num(1)
|
||||
local z1 = fp.num(0)
|
||||
|
||||
local z2 = fp.decode(random.random(32))
|
||||
local x2 = fp.mul(dx, z2)
|
||||
|
||||
-- Standard ladder.
|
||||
for i = #bits, 1, -1 do
|
||||
if bits[i] == 0 then
|
||||
x1, z1, x2, z2 = x25519.step(dx, x1, z1, x2, z2)
|
||||
x1, z1, x2, z2 = step(dx, x1, z1, x2, z2)
|
||||
else
|
||||
x2, z2, x1, z1 = x25519.step(dx, x2, z2, x1, z1)
|
||||
x2, z2, x1, z1 = step(dx, x2, z2, x1, z1)
|
||||
end
|
||||
end
|
||||
|
||||
-- Multiply by 8 (double 3 times).
|
||||
for _ = 1, 3 do
|
||||
x1, z1 = x25519.double(x1, z1)
|
||||
x1, z1 = double(x1, z1)
|
||||
end
|
||||
|
||||
return fp.mul(x1, fp.invert(z1))
|
||||
|
@ -53,7 +83,7 @@ function mod.publicKey(sk)
|
|||
expect(1, sk, "string")
|
||||
assert(#sk == 32, "secret key length must be 32")
|
||||
|
||||
return fp.encode(ladder8(x25519.G, bits(sk)))
|
||||
return fp.encode(ladder8(fp.num(9), bits(sk)))
|
||||
end
|
||||
|
||||
function mod.exchange(sk, pk)
|
||||
|
|
94
x25519c.lua
94
x25519c.lua
|
@ -1,94 +0,0 @@
|
|||
local expect = require "cc.expect".expect
|
||||
local fp = require "ccryptolib.internal.fp"
|
||||
local fq = require "ccryptolib.internal.fq"
|
||||
local x25519 = require "ccryptolib.internal.x25519"
|
||||
local maddq = require "ccryptolib.internal.maddq"
|
||||
local random = require "ccryptolib.random"
|
||||
|
||||
local ORDER = 4
|
||||
|
||||
--- The inverse of 8 modulo q (in montgomery form).
|
||||
local INV8Q = {
|
||||
5110253,
|
||||
3039345,
|
||||
2503500,
|
||||
11779568,
|
||||
15416472,
|
||||
16766550,
|
||||
16777215,
|
||||
16777215,
|
||||
16777215,
|
||||
16777215,
|
||||
4095,
|
||||
}
|
||||
|
||||
local function ladder8(dx, bits)
|
||||
local x1 = fp.num(1)
|
||||
local z1 = fp.num(0)
|
||||
|
||||
-- Compute a randomization factor for randomized projective coordinates.
|
||||
-- Biased but good enough.
|
||||
local rf = fp.decode(random.random(32))
|
||||
|
||||
local x2 = fp.mul(rf, dx)
|
||||
local z2 = rf
|
||||
|
||||
-- Standard ladder.
|
||||
for i = #bits, 1, -1 do
|
||||
if bits[i] == 0 then
|
||||
x1, z1, x2, z2 = x25519.step(dx, x1, z1, x2, z2)
|
||||
else
|
||||
x2, z2, x1, z1 = x25519.step(dx, x2, z2, x1, z1)
|
||||
end
|
||||
end
|
||||
|
||||
-- Multiply by 8 (double 3 times).
|
||||
for _ = 1, 3 do
|
||||
x1, z1 = x25519.double(x1, z1)
|
||||
end
|
||||
|
||||
return fp.mul(x1, fp.invert(z1))
|
||||
end
|
||||
|
||||
local mod = {}
|
||||
|
||||
function mod.new(sk)
|
||||
expect(1, sk, "string")
|
||||
assert(#sk == 32, "secret key length must be 32")
|
||||
|
||||
return maddq.new(fq.decodeClamped(sk), ORDER)
|
||||
end
|
||||
|
||||
function mod.encode(sks)
|
||||
return maddq.encode(sks)
|
||||
end
|
||||
|
||||
function mod.decode(str)
|
||||
expect(1, str, "string")
|
||||
assert(#str == 128, "encoded sks length must be 128")
|
||||
|
||||
return maddq.decode(str)
|
||||
end
|
||||
|
||||
function mod.remask(sks)
|
||||
return maddq.remask(sks)
|
||||
end
|
||||
|
||||
function mod.exchange(sks, pk, mc)
|
||||
expect(2, pk, "string")
|
||||
assert(#pk == 32, "public key length must be 32")
|
||||
expect(3, mc, "string")
|
||||
assert(#mc == 32, "multiplier length must be 32")
|
||||
|
||||
-- Reduce secret key using the multiplier.
|
||||
local skmc = maddq.unwrap(maddq.mul(sks, fq.decodeClamped(mc)))
|
||||
|
||||
-- Get bits.
|
||||
-- We have our exponent modulo q. We also know that its value is 0 modulo 8.
|
||||
-- Use the Chinese Remainder Theorem to find its value modulo 8q.
|
||||
local bits = fq.bits(fq.mul(skmc, INV8Q))
|
||||
|
||||
return fp.encode(ladder8(fp.decode(pk), bits))
|
||||
end
|
||||
|
||||
return mod
|
Loading…
Reference in a new issue