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:
Miguel Oliveira 2022-03-05 12:03:08 -03:00
parent ed8f66070f
commit 54b821c091
No known key found for this signature in database
GPG key ID: 2C2BE789E1377025
7 changed files with 272 additions and 552 deletions

View file

@ -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 fq = require "ccryptolib.internal.fq"
local ed25519 = require "ccryptolib.internal.ed25519"
local sha512 = require "ccryptolib.internal.sha512"
local expect = require "cc.expect".expect
local fp = require "ccryptolib.internal.fp"
local fq = require "ccryptolib.internal.fq"
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

View file

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

View file

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

View file

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

View file

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

View file

@ -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)

View file

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