Add masked signing
This commit is contained in:
parent
77892f3a76
commit
73e0df0b5e
|
@ -11,11 +11,6 @@ local random = require "ccryptolib.random"
|
||||||
|
|
||||||
local mod = {}
|
local mod = {}
|
||||||
|
|
||||||
--- Converts a signing key to an equivalent exchange key.
|
|
||||||
function mod.exchangeKey(sk)
|
|
||||||
return sha512.digest(sk):sub(1, 32)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Computes a public key from a secret key.
|
--- Computes a public key from a secret key.
|
||||||
--
|
--
|
||||||
-- @tparam string sk A random 32-byte secret key.
|
-- @tparam string sk A random 32-byte secret key.
|
||||||
|
@ -59,7 +54,7 @@ function mod.sign(sk, pk, msg)
|
||||||
|
|
||||||
-- Response.
|
-- Response.
|
||||||
local m = fq.decodeWide(random.random(64))
|
local m = fq.decodeWide(random.random(64))
|
||||||
local s = fq.add(fq.add(k, fq.mul(fq.add(x, m), e)), fq.mul(m, e))
|
local s = fq.sub(fq.add(k, fq.mul(fq.add(x, m), e)), fq.mul(m, e))
|
||||||
local sStr = fq.encode(s)
|
local sStr = fq.encode(s)
|
||||||
|
|
||||||
return rStr .. sStr
|
return rStr .. sStr
|
||||||
|
|
|
@ -276,6 +276,7 @@ return {
|
||||||
add = add,
|
add = add,
|
||||||
sub = sub,
|
sub = sub,
|
||||||
niels = niels,
|
niels = niels,
|
||||||
|
scale = scale,
|
||||||
encode = encode,
|
encode = encode,
|
||||||
decode = decode,
|
decode = decode,
|
||||||
mulG = mulG,
|
mulG = mulG,
|
||||||
|
|
|
@ -20,7 +20,7 @@ local ufq = packing.compileUnpack(fmtfq)
|
||||||
local ufql, fmtfql = packing.compileUnpack("<I3I3I3I3I3I3I3I3I3I3I3")
|
local ufql, fmtfql = packing.compileUnpack("<I3I3I3I3I3I3I3I3I3I3I3")
|
||||||
local ufqh, fmtfqh = packing.compileUnpack("<I3I3I3I3I3I3I3I3I3I3I1")
|
local ufqh, fmtfqh = packing.compileUnpack("<I3I3I3I3I3I3I3I3I3I3I1")
|
||||||
|
|
||||||
--- The scalar field's order, q.
|
--- The scalar field's order, q = 2²⁵² + 27742317777372353535851937790883648493.
|
||||||
local Q = {
|
local Q = {
|
||||||
16110573,
|
16110573,
|
||||||
06494812,
|
06494812,
|
||||||
|
@ -66,17 +66,17 @@ local T1 = {
|
||||||
}
|
}
|
||||||
|
|
||||||
local T8 = {
|
local T8 = {
|
||||||
01130678,
|
5110253,
|
||||||
05563041,
|
3039345,
|
||||||
03870191,
|
2503500,
|
||||||
01622646,
|
11779568,
|
||||||
01247520,
|
15416472,
|
||||||
12151703,
|
16766550,
|
||||||
16693196,
|
16777215,
|
||||||
09337410,
|
16777215,
|
||||||
04700637,
|
16777215,
|
||||||
07308819,
|
16777215,
|
||||||
00002083,
|
4095,
|
||||||
}
|
}
|
||||||
|
|
||||||
local ZERO = mp.num(0)
|
local ZERO = mp.num(0)
|
||||||
|
@ -139,7 +139,7 @@ end
|
||||||
--
|
--
|
||||||
-- @tparam {number...} a A number a as 11 limbs in [0..2²⁴).
|
-- @tparam {number...} a A number a as 11 limbs in [0..2²⁴).
|
||||||
-- @tparam {number...} b A number b < q as 11 limbs in [0..2²⁴).
|
-- @tparam {number...} b A number b < q as 11 limbs in [0..2²⁴).
|
||||||
-- @treturn 2⁻²⁶⁴ × a × b mod q as 11 limbs in [0..2²⁴).
|
-- @treturn {number...} 2⁻²⁶⁴ × a × b mod q as 11 limbs in [0..2²⁴).
|
||||||
--
|
--
|
||||||
local function mul(a, b)
|
local function mul(a, b)
|
||||||
local t0, t1 = mp.mul(a, b)
|
local t0, t1 = mp.mul(a, b)
|
||||||
|
@ -226,22 +226,12 @@ local function decodeClamped(str)
|
||||||
return montgomery(words)
|
return montgomery(words)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Decodes a scalar using the X25519/Ed25519 bit clamping scheme and division
|
--- Divides a scalar by 8.
|
||||||
-- by 8.
|
|
||||||
--
|
--
|
||||||
-- @tparam string str A 32-byte string encoding some little-endian number a.
|
-- @tparam {number...} 2²⁶⁴ × a mod q as 11 limbs in [0..2²⁴).
|
||||||
-- @treturn {number...} 2²⁶⁴ × clamp(a) ÷ 8 mod q as 11 limbs in [0..2²⁴).
|
-- @treturn {number...} 2²⁶⁵ × a ÷ 8 mod q as 11 limbs in [0..2²⁴).
|
||||||
--
|
local function eighth(a)
|
||||||
local function decodeClamped8(str)
|
return mul(a, T8)
|
||||||
-- Decode.
|
|
||||||
local words = {ufq(fmtfq, str, 1)} words[12] = nil
|
|
||||||
|
|
||||||
-- Clamp.
|
|
||||||
words[1] = bit32.band(words[1], 0xfffff8)
|
|
||||||
words[11] = bit32.band(words[11], 0x7fff)
|
|
||||||
words[11] = bit32.bor(words[11], 0x4000)
|
|
||||||
|
|
||||||
return mul(words, T8)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns a scalar in binary.
|
--- Returns a scalar in binary.
|
||||||
|
@ -398,8 +388,8 @@ return {
|
||||||
encode = encode,
|
encode = encode,
|
||||||
decode = decode,
|
decode = decode,
|
||||||
decodeWide = decodeWide,
|
decodeWide = decodeWide,
|
||||||
decodeClamped8 = decodeClamped8,
|
|
||||||
decodeClamped = decodeClamped,
|
decodeClamped = decodeClamped,
|
||||||
|
eighth = eighth,
|
||||||
bits = bits,
|
bits = bits,
|
||||||
makeRuleset = makeRuleset,
|
makeRuleset = makeRuleset,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,35 +2,56 @@ local expect = require "cc.expect".expect
|
||||||
local fq = require "ccryptolib.internal.fq"
|
local fq = require "ccryptolib.internal.fq"
|
||||||
local fp = require "ccryptolib.internal.fp"
|
local fp = require "ccryptolib.internal.fp"
|
||||||
local c25 = require "ccryptolib.internal.curve25519"
|
local c25 = require "ccryptolib.internal.curve25519"
|
||||||
|
local ed = require "ccryptolib.internal.edwards25519"
|
||||||
|
local sha512 = require "ccryptolib.internal.sha512"
|
||||||
local random = require "ccryptolib.random"
|
local random = require "ccryptolib.random"
|
||||||
|
|
||||||
local function mask(sk)
|
--- Transforms an X25519 secret key into a masked key.
|
||||||
|
local function maskExchangeSk(sk)
|
||||||
expect(1, sk, "string")
|
expect(1, sk, "string")
|
||||||
assert(#sk == 32, "secret key length must be 32")
|
assert(#sk == 32, "secret key length must be 32")
|
||||||
local mask = random.random(32)
|
local mask = random.random(32)
|
||||||
local x = fq.decodeClamped8(sk)
|
local x = fq.decodeClamped(sk)
|
||||||
local r = fq.decodeClamped8(mask)
|
local r = fq.decodeClamped(mask)
|
||||||
local xr = fq.sub(x, r)
|
local xr = fq.sub(x, r)
|
||||||
return fq.encode(xr), mask
|
return fq.encode(xr) .. mask
|
||||||
end
|
end
|
||||||
|
|
||||||
local function remask(msk, oldMask)
|
--- Transforms an Ed25519 secret key into a masked key.
|
||||||
expect(1, msk, "string")
|
function maskSignatureSk(sk)
|
||||||
assert(#msk == 32, "masked secret key length must be 32")
|
expect(1, sk, "string")
|
||||||
expect(2, oldMask, "string")
|
assert(#sk == 32, "secret key length must be 32")
|
||||||
assert(#oldMask == 32, "old mask length must be 32")
|
return maskExchangeSk(sha512.digest(sk):sub(1, 32))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Rerandomizes the masking on a masked key.
|
||||||
|
local function remask(sk)
|
||||||
|
expect(1, sk, "string")
|
||||||
|
assert(#sk == 64, "masked secret key length must be 64")
|
||||||
local newMask = random.random(32)
|
local newMask = random.random(32)
|
||||||
local xr = fq.decode(msk)
|
local xr = fq.decode(sk:sub(1, 32))
|
||||||
local r = fq.decodeClamped8(oldMask)
|
local r = fq.decodeClamped(sk:sub(33))
|
||||||
local s = fq.decodeClamped8(newMask)
|
local s = fq.decodeClamped(newMask)
|
||||||
local xs = fq.add(xr, fq.sub(r, s))
|
local xs = fq.add(xr, fq.sub(r, s))
|
||||||
return fq.encode(xs), newMask
|
return fq.encode(xs) .. newMask
|
||||||
end
|
end
|
||||||
|
|
||||||
local function exchangeOnPoint(msk, mask, P)
|
--- Returns the ephemeral exchange secret key of this masked key.
|
||||||
local xr = fq.decode(msk)
|
--
|
||||||
local r = fq.decodeClamped8(mask)
|
-- This is the second secret key in the "double key exchange" in @{exchange},
|
||||||
local rP, xrP, dP = c25.prac(P, fq.makeRuleset(r, xr))
|
-- the first being the key that has been masked. The ephemeral key changes every
|
||||||
|
-- time @{remask} is called.
|
||||||
|
--
|
||||||
|
local function exchangeEsk(sk)
|
||||||
|
expect(1, sk, "string")
|
||||||
|
assert(#sk == 64, "masked secret key length must be 64")
|
||||||
|
return sk:sub(33)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function exchangeOnPoint(sk, P)
|
||||||
|
local xr = fq.decode(sk:sub(1, 32))
|
||||||
|
local r = fq.decodeClamped(sk:sub(33))
|
||||||
|
local rP, xrP, dP = c25.prac(P, fq.makeRuleset(fq.eighth(r), fq.eighth(xr)))
|
||||||
|
|
||||||
-- Return early if P has small order or if r = xr. (1)
|
-- Return early if P has small order or if r = xr. (1)
|
||||||
if not rP then
|
if not rP then
|
||||||
|
@ -79,7 +100,24 @@ local function exchangeOnPoint(msk, mask, P)
|
||||||
return fp.encode(fp.mul(xPx, xPzInv)), fp.encode(fp.mul(rPx, rPzInv))
|
return fp.encode(fp.mul(xPx, xPzInv)), fp.encode(fp.mul(rPx, rPzInv))
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Treats both shares as X25519 keys and performs a double key exchange.
|
--- Returns the X25519 public key of this masked key.
|
||||||
|
local function exchangePk(sk)
|
||||||
|
expect(1, sk, "string")
|
||||||
|
assert(#sk == 64, "masked secret key length must be 64")
|
||||||
|
return (exchangeOnPoint(sk, c25.G))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns the Ed25519 public key of this masked key.
|
||||||
|
local function signaturePk(sk)
|
||||||
|
expect(1, sk, "string")
|
||||||
|
assert(#sk == 64, "masked secret key length must be 64")
|
||||||
|
local xr = fq.decode(sk:sub(1, 32))
|
||||||
|
local r = fq.decodeClamped(sk:sub(33))
|
||||||
|
local y = ed.add(ed.mulG(fq.bits(xr)), ed.niels(ed.mulG(fq.bits(r))))
|
||||||
|
return ed.encode(ed.scale(y))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Performs a double key exchange.
|
||||||
--
|
--
|
||||||
-- Returns 0 if the input public key has small order or if it isn't in the base
|
-- Returns 0 if the input public key has small order or if it isn't in the base
|
||||||
-- curve. This is different from standard X25519, which performs the exchange
|
-- curve. This is different from standard X25519, which performs the exchange
|
||||||
|
@ -88,30 +126,62 @@ end
|
||||||
-- May incorrectly return 0 with negligible chance if the mask happens to match
|
-- May incorrectly return 0 with negligible chance if the mask happens to match
|
||||||
-- the masked key. I haven't checked if clamping prevents that from happening.
|
-- the masked key. I haven't checked if clamping prevents that from happening.
|
||||||
--
|
--
|
||||||
local function exchange(msk, mask, pk)
|
local function exchange(sk, pk)
|
||||||
expect(1, msk, "string")
|
expect(1, sk, "string")
|
||||||
assert(#msk == 32, "masked secret key length must be 32")
|
assert(#sk == 64, "masked secret key length must be 64")
|
||||||
expect(2, mask, "string")
|
expect(2, pk, "string")
|
||||||
assert(#mask == 32, "mask length must be 32")
|
|
||||||
expect(3, pk, "string")
|
|
||||||
assert(#pk == 32, "public key length must be 32")
|
assert(#pk == 32, "public key length must be 32")
|
||||||
return exchangeOnPoint(msk, mask, c25.decode(pk))
|
return exchangeOnPoint(sk, c25.decode(pk))
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Same as @{exchange}, but decodes the public key as an Edwards25519 point.
|
--- Performs an exchange against an Ed25519 key.
|
||||||
local function exchangeEd(msk, mask, pk)
|
--
|
||||||
expect(1, msk, "string")
|
-- This is done by converting the key into X25519 before passing it to the
|
||||||
assert(#msk == 32, "masked secret key length must be 32")
|
-- regular exchange. Using this function on the result of @{signaturePk} leads
|
||||||
expect(2, mask, "string")
|
-- to the same value as using @{exchange} on the result of @{exchangePk}.
|
||||||
assert(#mask == 32, "mask length must be 32")
|
--
|
||||||
expect(3, pk, "string")
|
local function exchangeEd(sk, pk)
|
||||||
|
expect(1, sk, "string")
|
||||||
|
assert(#sk == 64, "masked secret key length must be 64")
|
||||||
|
expect(2, pk, "string")
|
||||||
assert(#pk == 32, "public key length must be 32")
|
assert(#pk == 32, "public key length must be 32")
|
||||||
return exchangeOnPoint(msk, mask, c25.decodeEd(pk))
|
return exchangeOnPoint(sk, c25.decodeEd(pk))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Signs a message using Ed25519.
|
||||||
|
local function sign(sk, pk, msg)
|
||||||
|
expect(1, sk, "string")
|
||||||
|
assert(#sk == 64, "masked secret key length must be 64")
|
||||||
|
expect(2, pk, "string")
|
||||||
|
assert(#pk == 32, "public key length must be 32")
|
||||||
|
expect(3, msg, "string")
|
||||||
|
|
||||||
|
-- Secret key.
|
||||||
|
local xr = fq.decode(sk:sub(1, 32))
|
||||||
|
local r = fq.decodeClamped(sk:sub(33))
|
||||||
|
|
||||||
|
-- Commitment.
|
||||||
|
local k = fq.decodeWide(random.random(64))
|
||||||
|
local rStr = ed.encode(ed.mulG(fq.bits(k)))
|
||||||
|
|
||||||
|
-- Challenge.
|
||||||
|
local e = fq.decodeWide(sha512.digest(rStr .. pk .. msg))
|
||||||
|
|
||||||
|
-- Response.
|
||||||
|
local s = fq.add(fq.add(k, fq.mul(xr, e)), fq.mul(r, e))
|
||||||
|
local sStr = fq.encode(s)
|
||||||
|
|
||||||
|
return rStr .. sStr
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mask = mask,
|
maskExchangeSk = maskExchangeSk,
|
||||||
|
maskSignatureSk = maskSignatureSk,
|
||||||
remask = remask,
|
remask = remask,
|
||||||
|
exchangePk = exchangePk,
|
||||||
|
exchangeEsk = exchangeEsk,
|
||||||
|
signaturePk = signaturePk,
|
||||||
exchange = exchange,
|
exchange = exchange,
|
||||||
exchangeEd = exchangeEd,
|
exchangeEd = exchangeEd,
|
||||||
|
sign = sign,
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ local util = require "spec.util"
|
||||||
local x25519c = require "ccryptolib.x25519c"
|
local x25519c = require "ccryptolib.x25519c"
|
||||||
|
|
||||||
local function exchange(sk, pk)
|
local function exchange(sk, pk)
|
||||||
local sk, ek = x25519c.mask(sk)
|
local sk = x25519c.maskExchangeSk(sk)
|
||||||
sk, ek = x25519c.remask(sk, ek)
|
sk = x25519c.remask(sk)
|
||||||
return (x25519c.exchange(sk, ek, pk))
|
return (x25519c.exchange(sk, pk))
|
||||||
end
|
end
|
||||||
|
|
||||||
describe("x25519c.exchange", function()
|
describe("x25519c.exchange", function()
|
||||||
|
|
Loading…
Reference in a new issue