Remove dependency on string.pack

This commit is contained in:
Miguel Oliveira 2022-10-16 19:28:56 -03:00
parent 0cd726952a
commit d4c173c713
10 changed files with 251 additions and 54 deletions

View file

@ -4,9 +4,12 @@
--
local expect = require "cc.expect".expect
local packing = require "ccryptolib.internal.packing"
local chacha20 = require "ccryptolib.chacha20"
local poly1305 = require "ccryptolib.poly1305"
local p8x1, fmt8x1 = packing.compilePack("<I8")
local u4x4, fmt4x4 = packing.compileUnpack("<I4I4I4I4")
local bxor = bit32.bxor
local bor = bit32.bor
@ -39,8 +42,8 @@ local function encrypt(key, nonce, message, aad, rounds)
-- Authenticate.
local pad1 = ("\0"):rep(-#aad % 16)
local pad2 = ("\0"):rep(-#ciphertext % 16)
local aadLen = ("<I8"):pack(#aad)
local ctxLen = ("<I8"):pack(#ciphertext)
local aadLen = p8x1("<I8", #aad)
local ctxLen = p8x1("<I8", #ciphertext)
local combined = aad .. pad1 .. ciphertext .. pad2 .. aadLen .. ctxLen
local tag = poly1305.mac(authKey, combined)
@ -77,11 +80,11 @@ local function decrypt(key, nonce, tag, ciphertext, aad, rounds)
-- Check tag.
local pad1 = ("\0"):rep(-#aad % 16)
local pad2 = ("\0"):rep(-#ciphertext % 16)
local aadLen = ("<I8"):pack(#aad)
local ctxLen = ("<I8"):pack(#ciphertext)
local aadLen = p8x1(fmt8x1, #aad)
local ctxLen = p8x1(fmt8x1, #ciphertext)
local combined = aad .. pad1 .. ciphertext .. pad2 .. aadLen .. ctxLen
local t1, t2, t3, t4 = ("<I4I4I4I4"):unpack(tag)
local u1, u2, u3, u4 = ("<I4I4I4I4"):unpack(poly1305.mac(authKey, combined))
local t1, t2, t3, t4 = u4x4(fmt4x4, tag, 1)
local u1, u2, u3, u4 = u4x4(fmt4x4, poly1305.mac(authKey, combined), 1)
local eq = bor(bxor(t1, u1), bxor(t2, u2), bxor(t3, u3), bxor(t4, u4))
if eq == 0 then return message end
end

View file

@ -3,11 +3,15 @@
-- @module blake3
--
local expect = require "cc.expect".expect
local expect = require "cc.expect".expect
local packing = require "ccryptolib.internal.packing"
local unpack = unpack or table.unpack
local bxor = bit32.bxor
local rol = bit32.lrotate
local p16x4, fmt16x4 = packing.compilePack("<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4")
local u16x4 = packing.compileUnpack(fmt16x4)
local u8x4, fmt8x4 = packing.compileUnpack("<I4I4I4I4I4I4I4I4")
local CHUNK_START = 0x01
local CHUNK_END = 0x02
@ -129,7 +133,7 @@ local function expand(state, len, offset)
for i = 0, len / 64 do
local n = offset + i
local md = compress(state.cv, state.m, n, state.n, state.f, true)
out[i + 1] = ("<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4"):pack(unpack(md))
out[i + 1] = p16x4(fmt16x4, unpack(md))
end
return table.concat(out):sub(1, len)
@ -150,7 +154,7 @@ local function update(state, message)
-- Digest complete blocks.
for i = 1, #blocks, 64 do
-- Compress the block.
local block = {("<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4"):unpack(blocks, i)}
local block = {u16x4(fmt16x4, blocks, i)}
local stateFlags = state.f + state.s + state.e
state.cv = compress(state.cv, block, state.t, 64, stateFlags)
state.s = 0
@ -188,7 +192,7 @@ local function finalize(state)
-- Pad the last message block.
local lastLen = #state.m
local padded = state.m .. ("\0"):rep(64)
local last = {("<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4"):unpack(padded)}
local last = {u16x4(fmt16x4, padded, 1)}
-- Prepare output expansion state.
if state.t > 0 then
@ -267,13 +271,13 @@ end
function mod.newKeyed(key)
expect(1, key, "string")
assert(#key == 32, "key length must be 32")
return new({("<I4I4I4I4I4I4I4I4"):unpack(key)}, KEYED_HASH)
return new({u8x4(fmt8x4, key, 1)}, KEYED_HASH)
end
function mod.newDk(context)
expect(1, context, "string")
local iv = new(IV, DERIVE_KEY_CONTEXT):update(context):finalize():expand(32)
return new({("<I4I4I4I4I4I4I4I4"):unpack(iv)}, DERIVE_KEY_MATERIAL)
return new({u8x4(fmt8x4, iv, 1)}, DERIVE_KEY_MATERIAL)
end
--- Hashes data using BLAKE3.
@ -304,7 +308,7 @@ function mod.digestKeyed(key, message, len)
expect(3, len, "number", "nil")
len = len or 32
assert(len % 1 == 0 and len >= 1, "length must be a positive integer")
local h = new({("<I4I4I4I4I4I4I4I4"):unpack(key)}, KEYED_HASH)
local h = new({u8x4(fmt8x4, key, 1)}, KEYED_HASH)
return h:update(message):finalize():expand(len)
end
@ -322,7 +326,7 @@ function mod.deriveKey(context)
expect(2, len, "number", "nil")
len = len or 32
assert(len % 1 == 0 and len >= 1, "length must be a positive integer")
local h = new({("<I4I4I4I4I4I4I4I4"):unpack(iv)}, DERIVE_KEY_MATERIAL)
local h = new({u8x4(fmt8x4, iv, 1)}, DERIVE_KEY_MATERIAL)
return h:update(material):finalize():expand(len)
end
end

View file

@ -3,10 +3,15 @@
-- @module chacha20
--
local expect = require "cc.expect".expect
local expect = require "cc.expect".expect
local packing = require "ccryptolib.internal.packing"
local bxor = bit32.bxor
local rol = bit32.lrotate
local u8x4, fmt8x4 = packing.compileUnpack("<I4I4I4I4I4I4I4I4")
local u3x4, fmt3x4 = packing.compileUnpack("<I4I4I4")
local p16x4, fmt16x4 = packing.compilePack("<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4")
local u16x4 = packing.compileUnpack(fmt16x4)
local mod = {}
@ -36,8 +41,8 @@ function mod.crypt(key, nonce, message, rounds, offset)
-- Build the state block.
local i0, i1, i2, i3 = 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
local k0, k1, k2, k3, k4, k5, k6, k7 = ("<I4I4I4I4I4I4I4I4"):unpack(key)
local cr, n0, n1, n2 = offset, ("<I4I4I4"):unpack(nonce)
local k0, k1, k2, k3, k4, k5, k6, k7 = u8x4(fmt8x4, key, 1)
local cr, n0, n1, n2 = offset, u3x4(fmt3x4, nonce, 1)
-- Pad the message.
local padded = message .. ("\0"):rep(-#message % 64)
@ -101,10 +106,10 @@ function mod.crypt(key, nonce, message, rounds, offset)
m00, m01, m02, m03, m04, m05, m06, m07,
m08, m09, m10, m11, m12, m13, m14, m15, idx =
("<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4"):unpack(padded, idx)
u16x4(fmt16x4, padded, idx)
-- Feed-forward and combine.
out[i] = ("<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4"):pack(
out[i] = p16x4(fmt16x4,
bxor(m00, s00 + i0), bxor(m01, s01 + i1),
bxor(m02, s02 + i2), bxor(m03, s03 + i3),
bxor(m04, s04 + k0), bxor(m05, s05 + k1),

View file

@ -10,7 +10,10 @@
-- @module[kind=internal] internal.fp
--
local packing = require "ccryptolib.inernal.packing"
local unpack = unpack or table.unpack
local ufp, fmtfp = packing.compileUnpack("<I3I3I2I3I3I2I3I3I2I3I3I2")
--- The modular square root of -1.
local I = {
@ -751,7 +754,7 @@ end
--
local function decode(b)
local w00, w01, w02, w03, w04, w05, w06, w07, w08, w09, w10, w11 =
("<I3I3I2I3I3I2I3I3I2I3I3I2"):unpack(b)
ufp(fmtfp, b, 1)
w11 = w11 % 2 ^ 15

View file

@ -10,10 +10,15 @@
-- @module[kind=internal] internal.fq
--
local mp = require "ccryptolib.internal.mp"
local util = require "ccryptolib.internal.util"
local mp = require "ccryptolib.internal.mp"
local util = require "ccryptolib.internal.util"
local packing = require "ccryptolib.internal.packing"
local unpack = unpack or table.unpack
local pfq, fmtfq = packing.compilePack("<I3I3I3I3I3I3I3I3I3I3I2")
local ufq = packing.compileUnpack(fmtfq)
local ufql, fmtfql = packing.compileUnpack("<I3I3I3I3I3I3I3I3I3I3I3")
local ufqh, fmtfqh = packing.compileUnpack("<I3I3I3I3I3I3I3I3I3I3I1")
--- The scalar field's order, q.
local Q = {
@ -180,7 +185,7 @@ end
-- @treturn string The 32-byte string encoding of a.
--
local function encode(a)
return ("<I3I3I3I3I3I3I3I3I3I3I2"):pack(unpack(demontgomery(a)))
return pfq(fmtfq, unpack(demontgomery(a)))
end
--- Decodes a scalar.
@ -189,7 +194,7 @@ end
-- @treturn {number...} 2²⁶⁴ × a mod q as 11 limbs in [0..2²⁴).
--
local function decode(str)
local dec = {("<I3I3I3I3I3I3I3I3I3I3I2"):unpack(str)} dec[12] = nil
local dec = {ufq(fmtfq, str, 1)} dec[12] = nil
return montgomery(dec)
end
@ -199,8 +204,8 @@ end
-- @treturn {number...} 2²⁶⁴ × a mod q as 11 limbs in [0..2²⁴).
--
local function decodeWide(str)
local low = {("<I3I3I3I3I3I3I3I3I3I3I3"):unpack(str)} low[12] = nil
local high = {("<I3I3I3I3I3I3I3I3I3I3I1"):unpack(str, 34)} high[12] = nil
local low = {ufql(fmtfql, str, 1)} low[12] = nil
local high = {ufqh(fmtfqh, str, 34)} high[12] = nil
return add(montgomery(low), montgomery(montgomery(high)))
end
@ -211,7 +216,7 @@ end
--
local function decodeClamped(str)
-- Decode.
local words = {("<I3I3I3I3I3I3I3I3I3I3I2"):unpack(str)} words[12] = nil
local words = {ufq(fmtfq, str, 1)} words[12] = nil
-- Clamp.
words[1] = bit32.band(words[1], 0xfffff8)
@ -229,7 +234,7 @@ end
--
local function decodeClamped8(str)
-- Decode.
local words = {("<I3I3I3I3I3I3I3I3I3I3I2"):unpack(str)} words[12] = nil
local words = {ufq(fmtfq, str, 1)} words[12] = nil
-- Clamp.
words[1] = bit32.band(words[1], 0xfffff8)

166
internal/packing.lua Normal file
View file

@ -0,0 +1,166 @@
--- High-performance binary packing of integers.
--
-- :::note Internal Module
-- This module is meant for internal use within the library. Its API is unstable
-- and subject to change without major version bumps.
-- :::
--
-- <br />
--
-- :::warning
-- For performance reasons, **the generated functions do not check types,
-- lengths, nor ranges**. You must ensure that the passed arguments are
-- well-formed and respect the format string yourself.
-- :::
--
-- <br />
--
-- @module[kind=internal] internal.packing
--
local fmt = string.format
local function mkPack(words, BE)
local out = "local C=string.char return function(_,"
local nb = 0
for i = 1, #words do
out = out .. fmt("n%d,", i)
nb = nb + words[i]
end
out = out:sub(1, -2) .. ")local "
for i = 1, nb do
out = out .. fmt("b%d,", i)
end
out = out:sub(1, -2) .. " "
local bi = 1
for i = 1, #words do
for _ = 1, words[i] - 1 do
out = out .. fmt("b%d=n%d%%2^8 n%d=(n%d-b%d)*2^-8 ", bi, i, i, i, bi)
bi = bi + 1
end
bi = bi + 1
end
out = out .. "return C("
bi = 1
if not BE then
for i = 1, #words do
for _ = 1, words[i] - 1 do
out = out .. fmt("b%d,", bi)
bi = bi + 1
end
out = out .. fmt("n%d%%2^8,", i)
bi = bi + 1
end
else
for i = 1, #words do
out = out .. fmt("n%d%%2^8,", i)
bi = bi + words[i] - 2
for _ = 1, words[i] - 1 do
out = out .. fmt("b%d,", bi)
bi = bi - 1
end
bi = bi + words[i] + 1
end
end
out = out:sub(1, -2) .. ")end"
return load(out)()
end
local function mkUnpack(words, BE)
local out = "local B=string.byte return function(_,s,i)local "
local bi = 1
if not BE then
for i = 1, #words do
for _ = 1, words[i] do
out = out .. fmt("b%d,", bi)
bi = bi + 1
end
end
else
for i = 1, #words do
bi = bi + words[i] - 1
for _ = 1, words[i] do
out = out .. fmt("b%d,", bi)
bi = bi - 1
end
bi = bi + words[i] + 1
end
end
out = out:sub(1, -2) .. fmt("=B(s,i,i+%d)return ", bi - 2)
bi = 1
for i = 1, #words do
out = out .. fmt("b%d", bi)
bi = bi + 1
for j = 2, words[i] do
out = out .. fmt("+b%d*2^%d", bi, 8 * j - 8)
bi = bi + 1
end
out = out .. ","
end
out = out .. fmt("i+%d end", bi - 1)
return load(out)()
end
local mod = {}
-- Check whether string.pack is implemented in a high-speed language.
if not string.pack or pcall(string.dump, string.pack) then
local function compile(fmt, fn)
local e = assert(fmt:match("^([><])I[I%d]+$"), "invalid format string")
local w = {}
for i in fmt:gmatch("I([%d]+)") do
local n = tonumber(i) or 4
assert(n > 0 and n <= 4, "integral size out of limits")
w[#w + 1] = n
end
return fn(w, e == ">")
end
local packCache = {}
local unpackCache = {}
--- (`string.pack == nil`) Compiles a binary packing function.
-- @tparam string fmt A string matched by `^([><])I[I%d]+$`.
-- @treturn function A high-performance function that behaves like an unsafe
-- version of `string.pack` for the given format string. Note that the third
-- argument isn't optional.
-- @treturn string fmt
-- @throws If the string is invalid or has an invalid integral size.
-- @throws If the compiled function is too large.
function mod.compilePack(fmt)
if not packCache[fmt] then
packCache[fmt] = compile(fmt, mkPack)
end
return packCache[fmt], fmt
end
--- (`string.pack == nil`) Compiles a binary unpacking function.
-- @tparam string fmt A string matched by `^([><])I[I%d]+$`.
-- @treturn function A high-performance function that behaves like an unsafe
-- version of `string.unpack` for the given format string.
-- @treturn string fmt
-- @throws If the string is invalid or has an invalid integral size.
-- @throws If the compiled function is too large.
function mod.compileUnpack(fmt)
if not unpackCache[fmt] then
unpackCache[fmt] = compile(fmt, mkUnpack)
end
return unpackCache[fmt], fmt
end
return mod
else
--- (`string.pack ~= nil`) Compiles a binary packing function.
-- @tparam string fmt
-- @treturn function `string.pack`
-- @treturn string fmt
mod.compilePack = function(fmt) return string.pack, fmt end
--- (`string.pack ~= nil`) Compiles a binary unpacking function.
-- @tparam string fmt
-- @treturn function `string.unpack`
-- @treturn string fmt
mod.compileUnpack = function(fmt) return string.unpack, fmt end
end
return mod

View file

@ -10,13 +10,17 @@
-- @module[kind=internal] internal.sha512
--
local expect = require "cc.expect".expect
local expect = require "cc.expect".expect
local packing = require "ccryptolib.internal.packing"
local shl = bit32.lshift
local shr = bit32.rshift
local bxor = bit32.bxor
local bnot = bit32.bnot
local band = bit32.band
local p1x16, fmt1x16 = packing.compilePack(">I16")
local p16x4, fmt16x4 = packing.compilePack(">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4")
local u32x4, fmt32x4 = packing.compileUnpack(">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4")
local function carry64(a1, a0)
local r0 = a0 % 2 ^ 32
@ -65,9 +69,7 @@ local function digest(data)
-- Pad input.
local bitlen = #data * 8
local padlen = -(#data + 17) % 128
data = data .. "\x80" .. ("\0"):rep(padlen) .. (">I16"):pack(bitlen)
local dataWords = {(">" .. ("I4"):rep(#data / 4)):unpack(data)}
dataWords[#dataWords] = nil
data = data .. "\x80" .. ("\0"):rep(padlen) .. p1x16(fmt1x16, bitlen)
-- Initialize state.
local h01, h00 = 0x6a09e667, 0xf3bcc908
@ -80,12 +82,8 @@ local function digest(data)
local h71, h70 = 0x5be0cd19, 0x137e2179
-- Digest.
local w = {}
for i = 1, #dataWords, 32 do
local index = i - 1
for j = 1, 32 do
w[j] = dataWords[index + j]
end
for i = 1, #data, 128 do
local w = {u32x4(fmt32x4, data, i)}
-- Message schedule.
for j = 33, 160, 2 do
@ -172,7 +170,7 @@ local function digest(data)
h71, h70 = carry64(h71 + h1, h70 + h0)
end
return (">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4"):pack(
return p16x4(fmt16x4,
h01, h00, h11, h10, h21, h20, h31, h30,
h41, h40, h51, h50, h61, h60, h71, h70
)

View file

@ -3,8 +3,12 @@
-- @module poly1305
--
local expect = require "cc.expect".expect
local random = require "ccryptolib.random"
local expect = require "cc.expect".expect
local random = require "ccryptolib.random"
local packing = require "ccryptolib.internal.packing"
local u4x4, fmt4x4 = packing.compileUnpack("<I4I4I4I4")
local p4x4 = packing.compilePack(fmt4x4)
local mod = {}
@ -27,7 +31,7 @@ function mod.mac(key, message)
end
-- Decode r.
local R0, R1, R2, R3 = ("<I4I4I4I4"):unpack(key)
local R0, R1, R2, R3 = u4x4(fmt4x4, key, 1)
-- Clamp and shift.
R0 = R0 % 2 ^ 28
@ -55,7 +59,7 @@ function mod.mac(key, message)
for i = 1, #message, 16 do
-- Decode message block.
local m0, m1, m2, m3 = ("<I4I4I4I4"):unpack(message, i)
local m0, m1, m2, m3 = u4x4(fmt4x4, message, i)
-- Shift message and add.
local x0 = h0 + h1 + m0
@ -119,7 +123,7 @@ function mod.mac(key, message)
end
-- Decode s.
local s0, s1, s2, s3 = ("<I4I4I4I4"):unpack(key, 17)
local s0, s1, s2, s3 = u4x4(fmt4x4, key, 17)
-- Add.
local t0 = s0 + c0 + c1 local u0 = t0 % 2 ^ 32
@ -128,7 +132,7 @@ function mod.mac(key, message)
local t3 = t2 - u2 + s3 * 2 ^ 96 + c6 + c7 local u3 = t3 % 2 ^ 128
-- Encode.
return ("<I4I4I4I4"):pack(u0, u1 / 2 ^ 32, u2 / 2 ^ 64, u3 / 2 ^ 96)
return p4x4(fmt4x4, u0, u1 / 2 ^ 32, u2 / 2 ^ 64, u3 / 2 ^ 96)
end
local mac = mod.mac

View file

@ -1,5 +1,8 @@
local blake3 = require "ccryptolib.blake3"
local chacha20 = require "ccryptolib.chacha20"
local packing = require "ccryptolib.internal.packing"
local u1x4, fmt1x4 = packing.compileUnpack("<I4")
-- Initialize from local context.
local ctx = {
@ -55,7 +58,7 @@ end
stir(512)
-- Save.
math.randomseed(("I4"):unpack(random(4)))
math.randomseed(u1x4(fmt1x4, random(4), 1))
save()
return {

View file

@ -3,7 +3,8 @@
-- @module sha256
--
local expect = require "cc.expect".expect
local expect = require "cc.expect".expect
local packing = require "ccryptolib.internal.packing"
local rol = bit32.lrotate
local shr = bit32.rshift
@ -11,6 +12,11 @@ local bxor = bit32.bxor
local bnot = bit32.bnot
local band = bit32.band
local unpack = unpack or table.unpack
local p1x8, fmt1x8 = packing.compilePack(">I8")
local p16x4, fmt16x4 = packing.compilePack(">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4")
local u16x4 = packing.compileUnpack(fmt16x4)
local p8x4, fmt8x4 = packing.compilePack(">I4I4I4I4I4I4I4I4")
local u8x4 = packing.compileUnpack(fmt8x4)
local function primes(n, exp)
local out = {}
@ -82,15 +88,15 @@ local function digest(data)
-- Pad input.
local bitlen = #data * 8
local padlen = -(#data + 9) % 64
data = data .. "\x80" .. ("\0"):rep(padlen) .. (">I8"):pack(bitlen)
data = data .. "\x80" .. ("\0"):rep(padlen) .. p1x8(fmt1x8, bitlen)
-- Digest.
local h = h0
for i = 1, #data, 64 do
h = compress(h, {(">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4"):unpack(data, i)})
h = compress(h, {u16x4(fmt16x4, data, i)})
end
return (">I4I4I4I4I4I4I4I4"):pack(unpack(h))
return p8x4(fmt8x4, unpack(h))
end
--- Hashes a password using PBKDF2-HMAC-SHA256.
@ -110,7 +116,7 @@ local function pbkdf2(password, salt, iter)
-- Pad password.
if #password > 64 then password = digest(password) end
password = password .. ("\0"):rep(-#password % 64)
password = {(">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4"):unpack(password)}
password = {u16x4(fmt16x4, password, 1)}
-- Compute password blocks.
local ikp = {}
@ -127,8 +133,8 @@ local function pbkdf2(password, salt, iter)
local pad96 = {2 ^ 31, 0, 0, 0, 0, 0, 0, 0x300}
-- First iteration.
local pre = (">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4"):pack(unpack(ikp))
local hs = {(">I4I4I4I4I4I4I4I4"):unpack(digest(pre .. salt .. "\0\0\0\1"))}
local pre = p16x4(fmt16x4, unpack(ikp))
local hs = {u8x4(fmt8x4, digest(pre .. salt .. "\0\0\0\1"), 1)}
for i = 1, 8 do hs[i + 8] = pad96[i] end
hs = compress(hokp, hs)
@ -142,7 +148,7 @@ local function pbkdf2(password, salt, iter)
for i = 1, 8 do out[i] = bxor(out[i], hs[i]) end
end
return (">I4I4I4I4I4I4I4I4"):pack(unpack(out))
return p8x4(fmt8x4, unpack(out))
end
return {