diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5249ca6..40463ee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,9 +41,15 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v3 with: - name: dist + name: dev path: dist/*.lua + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: release + path: dist/release/*.lua + # via https://github.com/softprops/action-gh-release/issues/270 - name: Create tag id: tag @@ -63,4 +69,6 @@ jobs: tag_name: ${{ steps.tag.outputs.tag }} files: | dist/telem.lua - dist/telem.min.lua \ No newline at end of file + dist/vendor.lua + dist/release/telem.min.lua + dist/release/vendor.min.lua \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3e4836f..9738d05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ src/telem/bin/helloWorld.lua dist/* +src.tar \ No newline at end of file diff --git a/LICENSE b/LICENSE index ea18b42..9695664 100644 --- a/LICENSE +++ b/LICENSE @@ -22,3 +22,6 @@ SOFTWARE. Portions of this software are copyright of their respective authors and released under the MIT license: - lua-object-model, Copyright 2015 Pavel Batečko (Shira). For licensing see src/telem/lib/LICENSE-ObjectModel.txt + - ECNet2, Copyright 2020 Miguel Oliveira. For licensing see src/telem/vendor/LICENSE-ECNet2.txt + - CCryptoLib, Copyright 2023 Miguel Oliveira. For licensing see src/telem/vendor/LICENSE-CCryptoLib.txt + - RedRun, By JackMacWindows. For licensing see src/telem/vendor/redrun.lua diff --git a/build.sh b/build.sh index 3551d95..54d44e8 100755 --- a/build.sh +++ b/build.sh @@ -1,12 +1,34 @@ #!/bin/bash +workdir=$PWD + +# echo $workdir + # glob the star shopt -s globstar mkdir -p dist -rm -rf dist/* -echo 'building...' +rm -rf dist/*\ + +echo 'building sources...' luacc telem.init -o dist/telem.lua -i src $(for i in src/telem/lib/**/*.lua; do echo $i; done | sed 'y/\//./;s/^src.//;s/\.lua$//') +echo 'building vendors...' + +cd $workdir/src/telem/vendor + +# build vendor package +luacc init -o $workdir/dist/vendor.lua -i . $(for i in **/*.lua; do echo $i; done | sed 'y/\//./;s/^src.vendor.//;s/\.lua$//' | awk '{ if ($1 != "init") { print } }') + +# patch redrun function +sed -i 's|\["redrun"\] = function()|["redrun"] = function(...)|g' $workdir/dist/vendor.lua + +cd $workdir + echo 'squishing...' -luamin -f dist/telem.lua > dist/telem.min.lua \ No newline at end of file +mkdir -p dist/release +luamin -f dist/telem.lua > dist/release/telem.min.lua +luamin -f dist/vendor.lua > dist/release/vendor.min.lua + +# echo 'tarring...' +# tar -cf src.tar src \ No newline at end of file diff --git a/src/telem/vendor/LICENSE-CCryptoLib.txt b/src/telem/vendor/LICENSE-CCryptoLib.txt new file mode 100644 index 0000000..adc21cb --- /dev/null +++ b/src/telem/vendor/LICENSE-CCryptoLib.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Miguel Oliveira + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/telem/vendor/LICENSE-ECNet.txt b/src/telem/vendor/LICENSE-ECNet2.txt similarity index 100% rename from src/telem/vendor/LICENSE-ECNet.txt rename to src/telem/vendor/LICENSE-ECNet2.txt diff --git a/src/telem/vendor/ccryptolib/aead.lua b/src/telem/vendor/ccryptolib/aead.lua new file mode 100644 index 0000000..26857cc --- /dev/null +++ b/src/telem/vendor/ccryptolib/aead.lua @@ -0,0 +1,93 @@ +--- The ChaCha20Poly1305AEAD authenticated encryption with associated data (AEAD) construction. + +local expect = require "cc.expect".expect +local lassert = require "ccryptolib.internal.util".lassert +local packing = require "ccryptolib.internal.packing" +local chacha20 = require "ccryptolib.chacha20" +local poly1305 = require "ccryptolib.poly1305" + +local p8x1, fmt8x1 = packing.compilePack("= 8, "round number must be no smaller than 8", 2) + lassert(rounds <= 20, "round number must be no larger than 20", 2) + + -- Generate auth key and encrypt. + local msgLong = ("\0"):rep(64) .. message + local ctxLong = chacha20.crypt(key, nonce, msgLong, rounds, 0) + local authKey = ctxLong:sub(1, 32) + local ciphertext = ctxLong:sub(65) + + -- Authenticate. + local pad1 = ("\0"):rep(-#aad % 16) + local pad2 = ("\0"):rep(-#ciphertext % 16) + local aadLen = p8x1("= 8, "round number must be no smaller than 8", 2) + lassert(rounds <= 20, "round number must be no larger than 20", 2) + + -- Generate auth key. + local authKey = chacha20.crypt(key, nonce, ("\0"):rep(32), rounds, 0) + + -- Check tag. + local pad1 = ("\0"):rep(-#aad % 16) + local pad2 = ("\0"):rep(-#ciphertext % 16) + local aadLen = p8x1(fmt8x1, #aad) + local ctxLen = p8x1(fmt8x1, #ciphertext) + local combined = aad .. pad1 .. ciphertext .. pad2 .. aadLen .. ctxLen + local t1, t2, t3, t4 = u4x4(fmt4x4, tag, 1) + local u1, u2, u3, u4 = u4x4(fmt4x4, poly1305.mac(authKey, combined), 1) + local eq = bxor(t1, u1) + bxor(t2, u2) + bxor(t3, u3) + bxor(t4, u4) + if eq ~= 0 then return nil end + + -- Decrypt + return chacha20.crypt(key, nonce, ciphertext, rounds) +end + +return { + encrypt = encrypt, + decrypt = decrypt, +} diff --git a/src/telem/vendor/ccryptolib/blake3.lua b/src/telem/vendor/ccryptolib/blake3.lua new file mode 100644 index 0000000..54a3e84 --- /dev/null +++ b/src/telem/vendor/ccryptolib/blake3.lua @@ -0,0 +1,255 @@ +--- The BLAKE3 cryptographic hash function. + +local expect = require "cc.expect".expect +local lassert = require "ccryptolib.internal.util".lassert +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(" 0 then + -- Root is a parent, digest last block now and merge parents. + local stateFlags = flags + stateStart + CHUNK_END + local mergeCv = compress(stateCv, last, stateT, lastLen, stateFlags) + for i = #stateCvs, 2, -1 do + local block = merge(stateCvs[i], mergeCv) + mergeCv = compress(iv, block, 0, 64, flags + PARENT) + end + + -- Set output state. + outCv = iv + outBlock = merge(stateCvs[1], mergeCv) + outLen = 64 + outFlags = flags + ROOT + PARENT + else + -- Root block is in the first chunk, set output state. + outCv = stateCv + outBlock = last + outLen = lastLen + outFlags = flags + stateStart + CHUNK_END + ROOT + end + + -- Expand output. + local out = {} + for i = 0, len / 64 do + local md = compress(outCv, outBlock, i, outLen, outFlags, true) + out[i + 1] = p16x4(fmt16x4, unpack(md)) + end + + return table.concat(out):sub(1, len) +end + +--- Hashes data using BLAKE3. +--- @param message string The input message. +--- @param len number? The desired hash length, in bytes. Defaults to 32. +--- @return string hash The hash. +local function digest(message, len) + expect(1, message, "string") + len = expect(2, len, "number", "nil") or 32 + lassert(len % 1 == 0, "desired output length must be an integer", 2) + lassert(len >= 1, "desired output length must be positive", 2) + return blake3(IV, 0, message, len) +end + +--- Performs a keyed hash. +--- @param key string A 32-byte random key. +--- @param message string The input message. +--- @param len number? The desired hash length, in bytes. Defaults to 32. +--- @return string hash The keyed hash. +local function digestKeyed(key, message, len) + expect(1, key, "string") + lassert(#key == 32, "key length must be 32", 2) + expect(2, message, "string") + len = expect(3, len, "number", "nil") or 32 + lassert(len % 1 == 0, "desired output length must be an integer", 2) + lassert(len >= 1, "desired output length must be positive", 2) + return blake3({u8x4(fmt8x4, key, 1)}, KEYED_HASH, message, len) +end + +--- Makes a context-based key derivation function (KDF). +--- @param context string The context for the KDF. +--- @return fun(material: string, len: number?): string kdf The KDF. +local function deriveKey(context) + expect(1, context, "string") + local iv = {u8x4(fmt8x4, blake3(IV, DERIVE_KEY_CONTEXT, context, 32), 1)} + + --- Derives a key. + --- @param material string The keying material. + --- @param len number? The desired hash length, in bytes. Defaults to 32. + return function(material, len) + expect(1, material, "string") + len = expect(2, len, "number", "nil") or 32 + lassert(len % 1 == 0, "desired output length must be an integer", 2) + lassert(len >= 1, "desired output length must be positive", 2) + return blake3(iv, DERIVE_KEY_MATERIAL, material, len) + end +end + +return { + digest = digest, + digestKeyed = digestKeyed, + deriveKey = deriveKey, +} diff --git a/src/telem/vendor/ccryptolib/chacha20.lua b/src/telem/vendor/ccryptolib/chacha20.lua new file mode 100644 index 0000000..fdc51d0 --- /dev/null +++ b/src/telem/vendor/ccryptolib/chacha20.lua @@ -0,0 +1,126 @@ +--- The ChaCha20 stream cipher. + +local expect = require "cc.expect".expect +local lassert = require "ccryptolib.internal.util".lassert +local packing = require "ccryptolib.internal.packing" + +local bxor = bit32.bxor +local rol = bit32.lrotate +local u8x4, fmt8x4 = packing.compileUnpack("= 8, "round number must be no smaller than 8", 2) + lassert(rounds <= 20, "round number must be no larger than 20", 2) + offset = expect(5, offset, "number", "nil") or 1 + lassert(offset % 1 == 0, "offset must be an integer", 2) + lassert(offset >= 0, "offset must be nonnegative", 2) + lassert(#message + 64 * offset <= 2 ^ 38, "offset too large", 2) + + -- Build the state block. + local i0, i1, i2, i3 = 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 + 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) + + -- Expand and combine. + local out = {} + local idx = 1 + for i = 1, #padded / 64 do + -- Copy the block. + local s00, s01, s02, s03 = i0, i1, i2, i3 + local s04, s05, s06, s07 = k0, k1, k2, k3 + local s08, s09, s10, s11 = k4, k5, k6, k7 + local s12, s13, s14, s15 = cr, n0, n1, n2 + + -- Iterate. + for _ = 1, rounds, 2 do + s00 = s00 + s04 s12 = rol(bxor(s12, s00), 16) + s08 = s08 + s12 s04 = rol(bxor(s04, s08), 12) + s00 = s00 + s04 s12 = rol(bxor(s12, s00), 8) + s08 = s08 + s12 s04 = rol(bxor(s04, s08), 7) + + s01 = s01 + s05 s13 = rol(bxor(s13, s01), 16) + s09 = s09 + s13 s05 = rol(bxor(s05, s09), 12) + s01 = s01 + s05 s13 = rol(bxor(s13, s01), 8) + s09 = s09 + s13 s05 = rol(bxor(s05, s09), 7) + + s02 = s02 + s06 s14 = rol(bxor(s14, s02), 16) + s10 = s10 + s14 s06 = rol(bxor(s06, s10), 12) + s02 = s02 + s06 s14 = rol(bxor(s14, s02), 8) + s10 = s10 + s14 s06 = rol(bxor(s06, s10), 7) + + s03 = s03 + s07 s15 = rol(bxor(s15, s03), 16) + s11 = s11 + s15 s07 = rol(bxor(s07, s11), 12) + s03 = s03 + s07 s15 = rol(bxor(s15, s03), 8) + s11 = s11 + s15 s07 = rol(bxor(s07, s11), 7) + + s00 = s00 + s05 s15 = rol(bxor(s15, s00), 16) + s10 = s10 + s15 s05 = rol(bxor(s05, s10), 12) + s00 = s00 + s05 s15 = rol(bxor(s15, s00), 8) + s10 = s10 + s15 s05 = rol(bxor(s05, s10), 7) + + s01 = s01 + s06 s12 = rol(bxor(s12, s01), 16) + s11 = s11 + s12 s06 = rol(bxor(s06, s11), 12) + s01 = s01 + s06 s12 = rol(bxor(s12, s01), 8) + s11 = s11 + s12 s06 = rol(bxor(s06, s11), 7) + + s02 = s02 + s07 s13 = rol(bxor(s13, s02), 16) + s08 = s08 + s13 s07 = rol(bxor(s07, s08), 12) + s02 = s02 + s07 s13 = rol(bxor(s13, s02), 8) + s08 = s08 + s13 s07 = rol(bxor(s07, s08), 7) + + s03 = s03 + s04 s14 = rol(bxor(s14, s03), 16) + s09 = s09 + s14 s04 = rol(bxor(s04, s09), 12) + s03 = s03 + s04 s14 = rol(bxor(s14, s03), 8) + s09 = s09 + s14 s04 = rol(bxor(s04, s09), 7) + end + + -- Decode message block. + local m00, m01, m02, m03, m04, m05, m06, m07 + local m08, m09, m10, m11, m12, m13, m14, m15 + + m00, m01, m02, m03, m04, m05, m06, m07, + m08, m09, m10, m11, m12, m13, m14, m15, idx = + u16x4(fmt16x4, padded, idx) + + -- Feed-forward and combine. + 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), + bxor(m06, s06 + k2), bxor(m07, s07 + k3), + bxor(m08, s08 + k4), bxor(m09, s09 + k5), + bxor(m10, s10 + k6), bxor(m11, s11 + k7), + bxor(m12, s12 + cr), bxor(m13, s13 + n0), + bxor(m14, s14 + n1), bxor(m15, s15 + n2) + ) + + -- Increment counter. + cr = cr + 1 + end + + return table.concat(out):sub(1, #message) +end + +return { + crypt = crypt, +} diff --git a/src/telem/vendor/ccryptolib/ed25519.lua b/src/telem/vendor/ccryptolib/ed25519.lua new file mode 100644 index 0000000..3ca677c --- /dev/null +++ b/src/telem/vendor/ccryptolib/ed25519.lua @@ -0,0 +1,86 @@ +--- The Ed25519 digital signature scheme. + +local expect = require "cc.expect".expect +local lassert = require "ccryptolib.internal.util".lassert +local fq = require "ccryptolib.internal.fq" +local sha512 = require "ccryptolib.internal.sha512" +local ed = require "ccryptolib.internal.edwards25519" +local random = require "ccryptolib.random" + +--- Computes a public key from a secret key. +--- @param sk string A random 32-byte secret key. +--- @return string pk The matching 32-byte public key. +local function publicKey(sk) + expect(1, sk, "string") + assert(#sk == 32, "secret key length must be 32") + + local h = sha512.digest(sk) + local x = fq.decodeClamped(h:sub(1, 32)) + + return ed.encode(ed.mulG(fq.bits(x))) +end + +--- Signs a message. +--- @param sk string The signer's secret key. +--- @param pk string The signer's public key. +--- @param msg string The message to be signed. +--- @return string sig The 64-byte signature on the message. +local function sign(sk, pk, msg) + expect(1, sk, "string") + lassert(#sk == 32, "secret key length must be 32", 2) + expect(2, pk, "string") + lassert(#pk == 32, "public key length must be 32", 2) + expect(3, msg, "string") + + -- Secret key. + local h = sha512.digest(sk) + local x = fq.decodeClamped(h:sub(1, 32)) + + -- Commitment. + local k = fq.decodeWide(random.random(64)) + local r = ed.mulG(fq.bits(k)) + local rStr = ed.encode(r) + + -- Challenge. + local e = fq.decodeWide(sha512.digest(rStr .. pk .. msg)) + + -- Response. + local m = fq.decodeWide(random.random(64)) + local s = fq.sub(fq.add(k, fq.mul(fq.add(x, m), e)), fq.mul(m, e)) + local sStr = fq.encode(s) + + return rStr .. sStr +end + +--- Verifies a signature on a message. +--- @param pk string The signer's public key. +--- @param msg string The signed message. +--- @param sig string The alleged signature. +--- @return boolean valid Whether the signature is valid or not. +local function verify(pk, msg, sig) + expect(1, pk, "string") + lassert(#pk == 32, "public key length must be 32", 2) --- @cast pk String32 + expect(2, msg, "string") + expect(3, sig, "string") + lassert(#sig == 64, "signature length must be 64", 2) + + local y = ed.decode(pk) + if not y then return false end + + local rStr = sig:sub(1, 32) + local sStr = sig:sub(33) + + local e = fq.decodeWide(sha512.digest(rStr .. pk .. msg)) + + local gs = ed.mulG(fq.bits(fq.decode(sStr))) + local ye = ed.mul(y, fq.bits(e)) + local rv = ed.sub(gs, ed.niels(ye)) + + return ed.encode(rv) == rStr +end + +return { + publicKey = publicKey, + sign = sign, + verify = verify, +} diff --git a/src/telem/vendor/ccryptolib/internal/curve25519.lua b/src/telem/vendor/ccryptolib/internal/curve25519.lua new file mode 100644 index 0000000..1aee70d --- /dev/null +++ b/src/telem/vendor/ccryptolib/internal/curve25519.lua @@ -0,0 +1,289 @@ +--- Point arithmetic on the Curve25519 Montgomery curve. + +local fp = require "ccryptolib.internal.fp" +local ed = require "ccryptolib.internal.edwards25519" +local random = require "ccryptolib.random" + +--- @class MtPoint A point class on Curve25519, in XZ coordinates. +--- @field [1] number[] The X coordinate. +--- @field [2] number[] The Z coordinate. + +--- Doubles a point. +--- @param P1 MtPoint The point to double. +--- @return MtPoint P2 P1 + P1. +local function double(P1) + local x1, z1 = P1[1], P1[2] + 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 + +--- Computes differential addition on two points. +--- @param DP MtPoint P1 - P2. +--- @param P1 MtPoint The first point to add. +--- @param P2 MtPoint The second point to add. +--- @return MtPoint P3 P1 + P2. +local function dadd(DP, P1, P2) + local dx, dz = DP[1], DP[2] + local x1, z1 = P1[1], P1[2] + local x2, z2 = P2[1], P2[2] + local a = fp.add(x1, z1) + local b = fp.sub(x1, z1) + 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 x3 = fp.mul(dz, fp.square(fp.add(da, cb))) + local z3 = fp.mul(dx, fp.square(fp.sub(da, cb))) + return {x3, z3} +end + +--- Performs a step on the Montgomery ladder. +--- @param DP MtPoint P1 - P2. +--- @param P1 MtPoint The first point. +--- @param P2 MtPoint The second point. +--- @return MtPoint P3 2A +--- @return MtPoint P4 A + B +local function step(DP, P1, P2) + local dx, dz = DP[1], DP[2] + local x1, z1 = P1[1], P1[2] + local x2, z2 = P2[1], P2[2] + 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.mul(dz, 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 ladder(DP, bits) + local P = {fp.num(1), fp.num(0)} + local Q = DP + + for i = #bits, 1, -1 do + if bits[i] == 0 then + P, Q = step(DP, P, Q) + else + Q, P = step(DP, Q, P) + end + end + + return P +end + +--- Performs a scalar multiplication operation with multiplication by 8. +--- @param P MtPoint The base point. +--- @param bits number[] The scalar multiplier, in little-endian bits. +--- @return MtPoint product The product, multiplied by 8. +local function ladder8(P, bits) + -- Randomize. + local rf = fp.decode(random.random(32) --[[@as String32, length is given]]) + P = {fp.mul(P[1], rf), fp.mul(P[2], rf)} + + -- Multiply. + return double(double(double(ladder(P, bits)))) +end + +--- Scales a point's coordinates. +--- @param P MtPoint The input point. +--- @return MtPoint Q The same point P, but with Z = 1. +local function scale(P) + return {fp.mul(P[1], fp.invert(P[2])), fp.num(1)} +end + +--- Encodes a scaled point. +--- @param P MtPoint The scaled point to encode. +--- @return string encoded P, encoded into a 32-byte string. +local function encode(P) + return fp.encode(P[1]) +end + +--- Decodes a point. +--- @param str String32 A 32-byte encoded point. +--- @return MtPoint pt The decoded point. +local function decode(str) + return {fp.decode(str), fp.num(1)} +end + +--- Decodes an Edwards25519 encoded point into Curve25519, ignoring the sign. +--- +--- There is a single exception: The identity point (0, 1), which gets mapped +--- into the 2-torsion point (0, 0), which isn't the identity of Curve25519. +--- +--- @param str String32 A 32-byte encoded Edwards25519 point. +--- @return MtPoint pt The decoded point, mapped into Curve25519. +local function decodeEd(str) + local y = fp.decode(str) + local n = fp.carry(fp.add(fp.num(1), y)) + local d = fp.carry(fp.sub(fp.num(1), y)) + if fp.eqz(d) then + return {fp.num(0), fp.num(1)} + else + return {n, d} + end +end + +--- Performs a scalar multiplication by the base point G. +--- @param bits number[] The scalar multiplier, in little-endian bits. +--- @return MtPoint product The product point. +local function mulG(bits) + -- Multiply by G on Edwards25519. + local P = ed.mulG(bits) + + -- Use the birational map to get the point on Curve25519. + -- Never fails since G is in the large group, and the exponent is clamped. + local Py, Pz = P[2], P[3] + local Rx = fp.carry(fp.add(Py, Pz)) + local Rz = fp.carry(fp.sub(Pz, Py)) + + return {Rx, Rz} +end + +--- Computes a twofold product from a ruleset. +--- +--- Returns nil if any of the results would be equal to the identity. +--- +--- @param P MtPoint The base point. +--- @param ruleset __TYPE_TODO The ruleset generated by scalars m, n. +--- @return MtPoint? A [8m]P. +--- @return MtPoint? B [8n]P. +--- @return MtPoint? C [8m]P - [8n]P. +local function prac(P, ruleset) + -- Randomize. + local rf = fp.decode(random.random(32) --[[@as String32, length is given]]) + local A = {fp.mul(P[1], rf), fp.mul(P[2], rf)} + + -- Start the base at [8]P. + local A = double(double(double(A))) + + -- Throw away small order points. + if fp.eqz(A[2]) then return end + + -- Now e = d = gcd(m, n). + -- Update A from [8]P to [8 * gcd(m, n)]P. + A = ladder(A, ruleset[1]) + + -- Reject rulesets where m = n. + local rules = ruleset[2] + if #rules == 0 then return end + + -- Evaluate the first rule. + -- Since e = d, this means A - B = C = O. Differential addition fails when + -- C = O, so we need to treat this case specially. + -- Note that rules 0 and 1 never happen last, since the algorithm would stop + -- one step earlier if they did: + -- - If after rule 0 we had e = d, then (d, e) → (e, d) would also mean that + -- e = d, so it stops one step earlier. + -- - If after rule 1 we had e = d, then (d, e) → ((2d - e)/3, (2e - d)/3) + -- would mean that (2d - e)/3 = (2e - d)/3, thus 2d - e = 2e - d, thus + -- 3d = 3e, thus d = e, so it stops one step earlier. + local B, C + local rule = rules[#rules] + if rule == 2 then + -- (A, B, C) ← (2A + B, B, 2A) = (3A, A, 2A) + local A2 = double(A) + A, B, C = dadd(A, A2, A), A, A2 + elseif rule == 3 or rule == 5 then + -- (A, B, C) ← (A + B, B, A) = (2A, A, A) + -- or (A, B, C) ← (2A, B, 2A - B) = (2A, A, A) + A, B, C = double(A), A, A + elseif rule == 6 then + -- (A, B, C) ← (3A + 3B, B, 3A + 2B) = (6A, A, 5A) + local A2 = double(A) + local A3 = dadd(A, A2, A) + A, B, C = double(A3), A, dadd(A, A3, A2) + elseif rule == 7 then + -- (A, B, C) ← (3A + 2B, B, 3A + B) = (5A, A, 4A) + local A2 = double(A) + local A3 = dadd(A, A2, A) + local A4 = double(A2) + A, B, C = dadd(A3, A4, A), A, A4 + elseif rule == 8 then + -- (A, B, C) ← (3A + B, B, 3A) = (4A, A, 3A) + local A2 = double(A) + local A3 = dadd(A, A2, A) + A, B, C = double(A2), A, A3 + else + -- (A, B, C) ← (A, 2B, A - 2B) = (A, 2A, A) + A, B, C = A, double(A), A + end + + -- Evaluate the other rules. + -- Let's assume addition is undefined here, this happens when A - B = O. + -- Since A = [d]P and B = [e]P, A = B happens when: + -- (1) P is on the large order base group and d ≡ e (mod q). + -- (2) P is on the large order twist group and d ≡ e (mod q'). + -- (3) P is on a small order group. + -- Case (3) never happens since we throw small order points away above. + -- Since 0 ≤ {d, e} < q < q', a modular equivalence here means an integer + -- equivalence. Therefore d = e. + -- However, the ruleset stops when d = e, therefore the algorithm must have + -- stopped earlier than when it did. Contradiction. + -- Therefore, addition is always defined. + -- Furthermore, the PRAC invariants mean that this product is the same as + -- if the points were multiplied separately. + for i = #rules - 1, 1, -1 do + local rule = rules[i] + if rule == 0 then + -- (A, B, C) ← (B, A, B - A) + A, B = B, A + elseif rule == 1 then + -- (A, B, C) ← (2A + B, A + 2B, A - B) + local AB = dadd(C, A, B) + A, B = dadd(B, AB, A), dadd(A, AB, B) + elseif rule == 2 then + -- (A, B, C) ← (2A + B, B, 2A) + A, C = dadd(B, dadd(C, A, B), A), double(A) + elseif rule == 3 then + -- (A, B, C) ← (A + B, B, A) + A, C = dadd(C, A, B), A + elseif rule == 5 then + -- (A, B, C) ← (2A, B, 2A - B) + A, C = double(A), dadd(B, A, C) + elseif rule == 6 then + -- (A, B, C) ← (3A + 3B, B, 3A + 2B) + local AB = dadd(C, A, B) + local AABB = double(AB) + A, C = dadd(AB, AABB, AB), dadd(dadd(A, AB, B), AABB, A) + elseif rule == 7 then + -- (A, B, C) ← (3A + 2B, B, 3A + B) + local AB = dadd(C, A, B) + local AAB = dadd(B, AB, A) + A, C = dadd(A, AAB, AB), dadd(AB, AAB, A) + elseif rule == 8 then + -- (A, B, C) ← (3A + B, B, 3A) + local AA = double(A) + A, C = dadd(C, AA, dadd(C, A, B)), dadd(A, AA, A) + else + -- (A, B, C) ← (A, 2B, A - 2B) + B, C = double(B), dadd(A, C, B) + end + end + + return A, B, C +end + +return { + G = {fp.num(9), fp.num(1)}, + dadd = dadd, + scale = scale, + encode = encode, + decode = decode, + decodeEd = decodeEd, + ladder8 = ladder8, + mulG = mulG, + prac = prac, +} diff --git a/src/telem/vendor/ccryptolib/internal/edwards25519.lua b/src/telem/vendor/ccryptolib/internal/edwards25519.lua new file mode 100644 index 0000000..29b8b3f --- /dev/null +++ b/src/telem/vendor/ccryptolib/internal/edwards25519.lua @@ -0,0 +1,302 @@ +--- Point arithmetic on the Edwards25519 Edwards curve. + +local fp = require "ccryptolib.internal.fp" + +local unpack = unpack or table.unpack + +--- @class EdPoint A point on Edwards25519, in extended coordinates. +--- @field [1] number[] The X coordinate. +--- @field [2] number[] The Y coordinate. +--- @field [3] number[] The Z coordinate. +--- @field [4] number[] The T coordinate. + +--- @class NsPoint A point on Edwards25519, in Niels' coordinates. +--- @field [1] number[] Preprocessed Y + X. +--- @field [2] number[] Preprocessed Y - X. +--- @field [3] number[] Preprocessed 2Z. +--- @field [4] number[] Preprocessed 2DT. + +local D = fp.mul(fp.num(-121665), fp.invert(fp.num(121666))) +local K = fp.kmul(D, 2) + +--- @type EdPoint +local O = {fp.num(0), fp.num(1), fp.num(1), fp.num(0)} +local G = nil + +--- Doubles a point. +--- @param P1 EdPoint The point to double. +--- @return EdPoint P2 P1 + P1. +local function double(P1) + -- Unsoundness: fp.sub(g, e), and fp.sub(d, i) break fp.sub's contract since + -- it doesn't accept an fp2. Although not ideal, in practice this doesn't + -- matter since fp.carry handles the larger sum. + local P1x, P1y, P1z = unpack(P1) + local a = fp.square(P1x) + local b = fp.square(P1y) + local c = fp.square(P1z) + local d = fp.add(c, c) + local e = fp.add(a, b) + local f = fp.add(P1x, P1y) + local g = fp.square(f) + local h = fp.carry(fp.sub(g, e)) + local i = fp.sub(b, a) + local j = fp.carry(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 + +--- Adds two points. +--- @param P1 EdPoint The first summand point. +--- @param N2 NsPoint The second summand point. +--- @return EdPoint P3 P1 + P2, where N2 = niels(P2). +local function add(P1, N2) + local P1x, P1y, P1z, P1t = unpack(P1) + local N1p, N1m, N1z, N1t = unpack(N2) + 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 + +--- Subtracts one point from another. +--- @param P1 EdPoint The first summand point. +--- @param N2 NsPoint The second summand point. +--- @return EdPoint P3 P1 - P2, where N2 = niels(P2). +local function sub(P1, N2) + local P1x, P1y, P1z, P1t = unpack(P1) + local N1p, N1m, N1z, N1t = unpack(N2) + 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 + +--- Computes the Niels representation of a point. +--- @param P1 EdPoint The input point. +--- @return NsPoint N1 Niels' precomputation applied to P1. +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 + +--- Scales a point. +--- @param P1 EdPoint The input point. +--- @return EdPoint P2 The same point as P1, but with Z = 1. +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 + +--- Encodes a scaled point. +--- @param P1 EdPoint The scaled point to encode. +--- @return string out P1 encoded as a 32-byte string. +local function encode(P1) + P1 = scale(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 + +--- Decodes a point. +--- @param str String32 A 32-byte encoded point. +--- @return EdPoint? P1 The decoded point, or nil if it isn't on the curve. +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.carry(fp.neg(P3x)) + end + local P3z = fp.num(1) + local P3t = fp.mul(P3x, P3y) + return {P3x, P3y, P3z, P3t} +end + +G = decode("Xfffffffffffffffffffffffffffffff") --[[@as EdPoint, G is valid]] + +--- Transforms little-endian bits into a signed radix-2^w form. +--- @param bits number[] +--- @param w number Log2 of the radix, must be at least 1. +--- @return number[] +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 + +--- Computes a multiplication table for radix-2^w form multiplication. +--- @param P EdPoint The base point. +--- @param w number Log2 of the radix, must be at least 1. +--- @return NsPoint[][] +local function radixWTable(P, w) + local out = {} + for i = 1, math.ceil(256 / 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 + +--- The radix logarithm of the precomputed table for G. +local G_W = 5 + +--- The precomputed multiplication table for G. +local G_TABLE = radixWTable(G, G_W) + +--- Transforms little-endian bits into a signed radix-2^w non-adjacent form. +--- +--- The returned array contains a 0 whenever a single doubling is needed, or an +--- odd integer when an addition with a multiple of the base is needed. +--- +--- @param bits number[] +--- @param w number Log2 of the radix, must be at least 1. +--- @return number[] +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 + +--- Computes a multiplication table for wNAF form multiplication. +--- @param P EdPoint The base point. +--- @param w number Log2 of the radix, must be at least 1. +--- @return NsPoint[] +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 + +--- Performs a scalar multiplication by the base point G. +--- @param bits number[] The scalar multiplicand little-endian bits. +--- @return EdPoint +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 + +--- Performs a scalar multiplication operation. +--- @param P EdPoint The base point. +--- @param bits number[] The scalar multiplicand little-endian bits. +--- @return EdPoint +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 { + double = double, + add = add, + sub = sub, + niels = niels, + scale = scale, + encode = encode, + decode = decode, + mulG = mulG, + mul = mul, +} diff --git a/src/telem/vendor/ccryptolib/internal/fp.lua b/src/telem/vendor/ccryptolib/internal/fp.lua new file mode 100644 index 0000000..65456e6 --- /dev/null +++ b/src/telem/vendor/ccryptolib/internal/fp.lua @@ -0,0 +1,772 @@ +--- Arithmetic on Curve25519's base field. + +local packing = require "ccryptolib.internal.packing" + +local unpack = unpack or table.unpack +local ufp, fmtfp = packing.compileUnpack("= 2 ^ 22 - 19 + then + return {19 - 2 ^ 22 + c00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + else + return {c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11} + end +end + +--- Returns whether two elements are the same. +--- @param a FpR1 +--- @param b FpR1 +--- @return boolean eq Whether a ≡ b (mod p). +local function eq(a, b) + local c = canonicalize(sub(a, b)) + for i = 1, 12 do if c[i] ~= 0 then return false end end + return true +end + +--- Multiplies two elements. +--- @param a FpR2 +--- @param b FpR2 +--- @return FpR1 c An element such that c ≡ a × b (mod p). +local function mul(a, b) + local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a) + local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11 = unpack(b) + local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11 + + -- Multiply high half into c00..c11. + c00 = a11 * b01 + + a10 * b02 + + a09 * b03 + + a08 * b04 + + a07 * b05 + + a06 * b06 + + a05 * b07 + + a04 * b08 + + a03 * b09 + + a02 * b10 + + a01 * b11 + c01 = a11 * b02 + + a10 * b03 + + a09 * b04 + + a08 * b05 + + a07 * b06 + + a06 * b07 + + a05 * b08 + + a04 * b09 + + a03 * b10 + + a02 * b11 + c02 = a11 * b03 + + a10 * b04 + + a09 * b05 + + a08 * b06 + + a07 * b07 + + a06 * b08 + + a05 * b09 + + a04 * b10 + + a03 * b11 + c03 = a11 * b04 + + a10 * b05 + + a09 * b06 + + a08 * b07 + + a07 * b08 + + a06 * b09 + + a05 * b10 + + a04 * b11 + c04 = a11 * b05 + + a10 * b06 + + a09 * b07 + + a08 * b08 + + a07 * b09 + + a06 * b10 + + a05 * b11 + c05 = a11 * b06 + + a10 * b07 + + a09 * b08 + + a08 * b09 + + a07 * b10 + + a06 * b11 + c06 = a11 * b07 + + a10 * b08 + + a09 * b09 + + a08 * b10 + + a07 * b11 + c07 = a11 * b08 + + a10 * b09 + + a09 * b10 + + a08 * b11 + c08 = a11 * b09 + + a10 * b10 + + a09 * b11 + c09 = a11 * b10 + + a10 * b11 + c10 = a11 * b11 + + -- Multiply low half with reduction into c00..c11. + c00 = c00 * (19 / 2 ^ 255) + + a00 * b00 + c01 = c01 * (19 / 2 ^ 255) + + a01 * b00 + + a00 * b01 + c02 = c02 * (19 / 2 ^ 255) + + a02 * b00 + + a01 * b01 + + a00 * b02 + c03 = c03 * (19 / 2 ^ 255) + + a03 * b00 + + a02 * b01 + + a01 * b02 + + a00 * b03 + c04 = c04 * (19 / 2 ^ 255) + + a04 * b00 + + a03 * b01 + + a02 * b02 + + a01 * b03 + + a00 * b04 + c05 = c05 * (19 / 2 ^ 255) + + a05 * b00 + + a04 * b01 + + a03 * b02 + + a02 * b03 + + a01 * b04 + + a00 * b05 + c06 = c06 * (19 / 2 ^ 255) + + a06 * b00 + + a05 * b01 + + a04 * b02 + + a03 * b03 + + a02 * b04 + + a01 * b05 + + a00 * b06 + c07 = c07 * (19 / 2 ^ 255) + + a07 * b00 + + a06 * b01 + + a05 * b02 + + a04 * b03 + + a03 * b04 + + a02 * b05 + + a01 * b06 + + a00 * b07 + c08 = c08 * (19 / 2 ^ 255) + + a08 * b00 + + a07 * b01 + + a06 * b02 + + a05 * b03 + + a04 * b04 + + a03 * b05 + + a02 * b06 + + a01 * b07 + + a00 * b08 + c09 = c09 * (19 / 2 ^ 255) + + a09 * b00 + + a08 * b01 + + a07 * b02 + + a06 * b03 + + a05 * b04 + + a04 * b05 + + a03 * b06 + + a02 * b07 + + a01 * b08 + + a00 * b09 + c10 = c10 * (19 / 2 ^ 255) + + a10 * b00 + + a09 * b01 + + a08 * b02 + + a07 * b03 + + a06 * b04 + + a05 * b05 + + a04 * b06 + + a03 * b07 + + a02 * b08 + + a01 * b09 + + a00 * b10 + c11 = a11 * b00 + + a10 * b01 + + a09 * b02 + + a08 * b03 + + a07 * b04 + + a06 * b05 + + a05 * b06 + + a04 * b07 + + a03 * b08 + + a02 * b09 + + a01 * b10 + + a00 * b11 + + -- Carry and reduce. + a10 = c10 + 3 * 2 ^ 285 - 3 * 2 ^ 285 c11 = c11 + a10 + a11 = c11 + 3 * 2 ^ 306 - 3 * 2 ^ 306 c00 = c00 + 19 / 2 ^ 255 * a11 + + a00 = c00 + 3 * 2 ^ 73 - 3 * 2 ^ 73 c01 = c01 + a00 + a01 = c01 + 3 * 2 ^ 94 - 3 * 2 ^ 94 c02 = c02 + a01 + a02 = c02 + 3 * 2 ^ 115 - 3 * 2 ^ 115 c03 = c03 + a02 + a03 = c03 + 3 * 2 ^ 136 - 3 * 2 ^ 136 c04 = c04 + a03 + a04 = c04 + 3 * 2 ^ 158 - 3 * 2 ^ 158 c05 = c05 + a04 + a05 = c05 + 3 * 2 ^ 179 - 3 * 2 ^ 179 c06 = c06 + a05 + a06 = c06 + 3 * 2 ^ 200 - 3 * 2 ^ 200 c07 = c07 + a06 + a07 = c07 + 3 * 2 ^ 221 - 3 * 2 ^ 221 c08 = c08 + a07 + a08 = c08 + 3 * 2 ^ 243 - 3 * 2 ^ 243 c09 = c09 + a08 + a09 = c09 + 3 * 2 ^ 264 - 3 * 2 ^ 264 c10 = c10 - a10 + a09 + a10 = c10 + 3 * 2 ^ 285 - 3 * 2 ^ 285 c11 = c11 - a11 + a10 + + a11 = c11 + 3 * 2 ^ 306 - 3 * 2 ^ 306 + + return { + c00 - a00 + 19 / 2 ^ 255 * a11, + c01 - a01, + c02 - a02, + c03 - a03, + c04 - a04, + c05 - a05, + c06 - a06, + c07 - a07, + c08 - a08, + c09 - a09, + c10 - a10, + c11 - a11, + } +end + +--- Squares an element. +--- @param a FpR2 +--- @return FpR1 b An element such that b ≡ a² (mod p). +local function square(a) + local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a) + local d00, d01, d02, d03, d04, d05, d06, d07, d08, d09, d10 + local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11 + + -- Compute 2a. + d00 = a00 + a00 + d01 = a01 + a01 + d02 = a02 + a02 + d03 = a03 + a03 + d04 = a04 + a04 + d05 = a05 + a05 + d06 = a06 + a06 + d07 = a07 + a07 + d08 = a08 + a08 + d09 = a09 + a09 + d10 = a10 + a10 + + -- Multiply high half into c00..c11. + c00 = a11 * d01 + + a10 * d02 + + a09 * d03 + + a08 * d04 + + a07 * d05 + + a06 * a06 + c01 = a11 * d02 + + a10 * d03 + + a09 * d04 + + a08 * d05 + + a07 * d06 + c02 = a11 * d03 + + a10 * d04 + + a09 * d05 + + a08 * d06 + + a07 * a07 + c03 = a11 * d04 + + a10 * d05 + + a09 * d06 + + a08 * d07 + c04 = a11 * d05 + + a10 * d06 + + a09 * d07 + + a08 * a08 + c05 = a11 * d06 + + a10 * d07 + + a09 * d08 + c06 = a11 * d07 + + a10 * d08 + + a09 * a09 + c07 = a11 * d08 + + a10 * d09 + c08 = a11 * d09 + + a10 * a10 + c09 = a11 * d10 + c10 = a11 * a11 + + -- Multiply low half with reduction into c00..c11. + c00 = c00 * (19 / 2 ^ 255) + + a00 * a00 + c01 = c01 * (19 / 2 ^ 255) + + a01 * d00 + c02 = c02 * (19 / 2 ^ 255) + + a02 * d00 + + a01 * a01 + c03 = c03 * (19 / 2 ^ 255) + + a03 * d00 + + a02 * d01 + c04 = c04 * (19 / 2 ^ 255) + + a04 * d00 + + a03 * d01 + + a02 * a02 + c05 = c05 * (19 / 2 ^ 255) + + a05 * d00 + + a04 * d01 + + a03 * d02 + c06 = c06 * (19 / 2 ^ 255) + + a06 * d00 + + a05 * d01 + + a04 * d02 + + a03 * a03 + c07 = c07 * (19 / 2 ^ 255) + + a07 * d00 + + a06 * d01 + + a05 * d02 + + a04 * d03 + c08 = c08 * (19 / 2 ^ 255) + + a08 * d00 + + a07 * d01 + + a06 * d02 + + a05 * d03 + + a04 * a04 + c09 = c09 * (19 / 2 ^ 255) + + a09 * d00 + + a08 * d01 + + a07 * d02 + + a06 * d03 + + a05 * d04 + c10 = c10 * (19 / 2 ^ 255) + + a10 * d00 + + a09 * d01 + + a08 * d02 + + a07 * d03 + + a06 * d04 + + a05 * a05 + c11 = a11 * d00 + + a10 * d01 + + a09 * d02 + + a08 * d03 + + a07 * d04 + + a06 * d05 + + -- Carry and reduce. + a10 = c10 + 3 * 2 ^ 285 - 3 * 2 ^ 285 c11 = c11 + a10 + a11 = c11 + 3 * 2 ^ 306 - 3 * 2 ^ 306 c00 = c00 + 19 / 2 ^ 255 * a11 + + a00 = c00 + 3 * 2 ^ 73 - 3 * 2 ^ 73 c01 = c01 + a00 + a01 = c01 + 3 * 2 ^ 94 - 3 * 2 ^ 94 c02 = c02 + a01 + a02 = c02 + 3 * 2 ^ 115 - 3 * 2 ^ 115 c03 = c03 + a02 + a03 = c03 + 3 * 2 ^ 136 - 3 * 2 ^ 136 c04 = c04 + a03 + a04 = c04 + 3 * 2 ^ 158 - 3 * 2 ^ 158 c05 = c05 + a04 + a05 = c05 + 3 * 2 ^ 179 - 3 * 2 ^ 179 c06 = c06 + a05 + a06 = c06 + 3 * 2 ^ 200 - 3 * 2 ^ 200 c07 = c07 + a06 + a07 = c07 + 3 * 2 ^ 221 - 3 * 2 ^ 221 c08 = c08 + a07 + a08 = c08 + 3 * 2 ^ 243 - 3 * 2 ^ 243 c09 = c09 + a08 + a09 = c09 + 3 * 2 ^ 264 - 3 * 2 ^ 264 c10 = c10 - a10 + a09 + a10 = c10 + 3 * 2 ^ 285 - 3 * 2 ^ 285 c11 = c11 - a11 + a10 + + a11 = c11 + 3 * 2 ^ 306 - 3 * 2 ^ 306 + + return { + c00 - a00 + 19 / 2 ^ 255 * a11, + c01 - a01, + c02 - a02, + c03 - a03, + c04 - a04, + c05 - a05, + c06 - a06, + c07 - a07, + c08 - a08, + c09 - a09, + c10 - a10, + c11 - a11, + } +end + +--- Multiplies an element by a number. +--- @param a FpR2 +--- @param k number A number in [0..2²²). +--- @return FpR1 c An element such that c ≡ a × k (mod p). +local function kmul(a, k) + local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a) + local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11 + + -- TODO Range check. + a00 = a00 * k + a01 = a01 * k + a02 = a02 * k + a03 = a03 * k + a04 = a04 * k + a05 = a05 * k + a06 = a06 * k + a07 = a07 * k + a08 = a08 * k + a09 = a09 * k + a10 = a10 * k + a11 = a11 * k + + c11 = a11 + 3 * 2 ^ 306 - 3 * 2 ^ 306 a00 = a00 + 19 / 2 ^ 255 * c11 + + c00 = a00 + 3 * 2 ^ 73 - 3 * 2 ^ 73 a01 = a01 + c00 + c01 = a01 + 3 * 2 ^ 94 - 3 * 2 ^ 94 a02 = a02 + c01 + c02 = a02 + 3 * 2 ^ 115 - 3 * 2 ^ 115 a03 = a03 + c02 + c03 = a03 + 3 * 2 ^ 136 - 3 * 2 ^ 136 a04 = a04 + c03 + c04 = a04 + 3 * 2 ^ 158 - 3 * 2 ^ 158 a05 = a05 + c04 + c05 = a05 + 3 * 2 ^ 179 - 3 * 2 ^ 179 a06 = a06 + c05 + c06 = a06 + 3 * 2 ^ 200 - 3 * 2 ^ 200 a07 = a07 + c06 + c07 = a07 + 3 * 2 ^ 221 - 3 * 2 ^ 221 a08 = a08 + c07 + c08 = a08 + 3 * 2 ^ 243 - 3 * 2 ^ 243 a09 = a09 + c08 + c09 = a09 + 3 * 2 ^ 264 - 3 * 2 ^ 264 a10 = a10 + c09 + c10 = a10 + 3 * 2 ^ 285 - 3 * 2 ^ 285 a11 = a11 - c11 + c10 + + c11 = a11 + 3 * 2 ^ 306 - 3 * 2 ^ 306 + + return { + a00 - c00 + 19 / 2 ^ 255 * c11, + a01 - c01, + a02 - c02, + a03 - c03, + a04 - c04, + a05 - c05, + a06 - c06, + a07 - c07, + a08 - c08, + a09 - c09, + a10 - c10, + a11 - c11 + } +end + +--- Squares an element n times. +--- @param a FpR2 +--- @param n number The number of times to square a. +--- @return FpR1 c A number c such that c ≡ a ^ 2 ^ n (mod p). +local function nsquare(a, n) + for _ = 1, n do a = square(a) end + return a +end + +--- Computes the inverse of an element. +--- +--- Performance: 11 multiplications and 252 squarings. +--- +--- @param a FpR2 +--- @return FpR1 c An element such that c ≡ a⁻¹ (mod p), or 0 if c doesn't exist. +local function invert(a) + local a2 = square(a) + local a9 = mul(a, nsquare(a2, 2)) + local a11 = mul(a9, a2) + + local x5 = mul(square(a11), a9) + local x10 = mul(nsquare(x5, 5), x5) + local x20 = mul(nsquare(x10, 10), x10) + local x40 = mul(nsquare(x20, 20), x20) + local x50 = mul(nsquare(x40, 10), x10) + local x100 = mul(nsquare(x50, 50), x50) + local x200 = mul(nsquare(x100, 100), x100) + local x250 = mul(nsquare(x200, 50), x50) + + return mul(nsquare(x250, 5), a11) +end + +--- Returns an element x that satisfies vx² = u. +--- +--- Note that when v = 0, the returned element can take any value. +--- +--- @param u FpR2 +--- @param v FpR2 +--- @return FpR1? x An element such that vx² ≡ u (mod p), if it exists. +local function sqrtDiv(u, v) + u = carry(u) + + local v2 = square(v) + local v3 = mul(v, v2) + local v6 = square(v3) + local v7 = mul(v, v6) + local uv7 = mul(u, v7) + + local x2 = mul(square(uv7), uv7) + local x4 = mul(nsquare(x2, 2), x2) + local x8 = mul(nsquare(x4, 4), x4) + local x16 = mul(nsquare(x8, 8), x8) + local x18 = mul(nsquare(x16, 2), x2) + local x32 = mul(nsquare(x16, 16), x16) + local x50 = mul(nsquare(x32, 18), x18) + local x100 = mul(nsquare(x50, 50), x50) + local x200 = mul(nsquare(x100, 100), x100) + local x250 = mul(nsquare(x200, 50), x50) + local pr = mul(nsquare(x250, 2), uv7) + + local uv3 = mul(u, v3) + local b = mul(uv3, pr) + local b2 = square(b) + local vb2 = mul(v, b2) + + if not eq(vb2, u) then + -- Found sqrt(-u/v), multiply by i. + b = mul(b, I) + b2 = square(b) + vb2 = mul(v, b2) + end + + if eq(vb2, u) then + return b + else + return nil + end +end + +--- @class String32: string A string with length equal to 32 bytes. + +--- Encodes an element in little-endian. +--- @param a FpR1 +--- @return String32 out The 32-byte canonical encoding of a. +local function encode(a) + a = canonicalize(a) + local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a) + + local bytes = {} + local acc = a00 + + local function putBytes(n) + for _ = 1, n do + local byte = acc % 256 + bytes[#bytes + 1] = byte + acc = (acc - byte) / 256 + end + end + + putBytes(2) acc = acc + a01 / 2 ^ 16 + putBytes(3) acc = acc + a02 / 2 ^ 40 + putBytes(3) acc = acc + a03 / 2 ^ 64 + putBytes(2) acc = acc + a04 / 2 ^ 80 + putBytes(3) acc = acc + a05 / 2 ^ 104 + putBytes(3) acc = acc + a06 / 2 ^ 128 + putBytes(2) acc = acc + a07 / 2 ^ 144 + putBytes(3) acc = acc + a08 / 2 ^ 168 + putBytes(3) acc = acc + a09 / 2 ^ 192 + putBytes(2) acc = acc + a10 / 2 ^ 208 + putBytes(3) acc = acc + a11 / 2 ^ 232 + putBytes(3) + + return string.char(unpack(bytes)) --[[@as String32, putBytes sums to 32]] +end + +--- Decodes an element in little-endian. +--- @param b String32 A 32-byte string, the most-significant bit is discarded. +--- @return FpR1 out The decoded element. It may not be canonical. +local function decode(b) + local w00, w01, w02, w03, w04, w05, w06, w07, w08, w09, w10, w11 = + ufp(fmtfp, b, 1) + + w11 = w11 % 2 ^ 15 + + return carry { + w00, + w01 * 2 ^ 24, + w02 * 2 ^ 48, + w03 * 2 ^ 64, + w04 * 2 ^ 88, + w05 * 2 ^ 112, + w06 * 2 ^ 128, + w07 * 2 ^ 152, + w08 * 2 ^ 176, + w09 * 2 ^ 192, + w10 * 2 ^ 216, + w11 * 2 ^ 240, + } +end + +--- Checks if the given element is equal to 0. +--- @param a FpR2 +--- @return boolean eqz Whether a ≡ 0 (mod p). +local function eqz(a) + local c = canonicalize(a) + local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11 = unpack(c) + return c00 + c01 + c02 + c03 + c04 + c05 + c06 + c07 + c08 + c09 + c10 + c11 + == 0 +end + +return { + num = num, + neg = neg, + add = add, + sub = sub, + kmul = kmul, + mul = mul, + canonicalize = canonicalize, + square = square, + carry = carry, + invert = invert, + sqrtDiv = sqrtDiv, + encode = encode, + decode = decode, + eqz = eqz, +} diff --git a/src/telem/vendor/ccryptolib/internal/fq.lua b/src/telem/vendor/ccryptolib/internal/fq.lua new file mode 100644 index 0000000..2dac2be --- /dev/null +++ b/src/telem/vendor/ccryptolib/internal/fq.lua @@ -0,0 +1,370 @@ +--- Arithmetic on Curve25519's scalar field. + +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("= q means c - q >= 0. + -- Since q < 2²⁸⁸, c < 2q means c - q < q < 2²⁸⁸. + -- c's limbs fit in (-2²⁶..2²⁶), since subtraction adds at most one bit. + return (mp.carry(c)) -- cc < q implies that the carry number is 0. +end + +--- Adds two scalars mod q. +-- +-- If the two operands are in Montgomery form, returns the correct result also +-- in Montgomery form, since (2²⁶⁴ × a) + (2²⁶⁴ × b) ≡ 2²⁶⁴ × (a + b) (mod q). +-- +-- @tparam {number...} a A number a < q as 11 limbs in [0..2²⁴). +-- @tparam {number...} b A number b < q as 11 limbs in [0..2²⁴). +-- @treturn {number...} a + b mod q as 11 limbs in [0..2²⁴). +-- +local function add(a, b) + return reduce(mp.add(a, b)) +end + +--- Negates a scalar mod q. +-- +-- @tparam {number...} a A number a < q as 11 limbs in [0..2²⁴). +-- @treturn {number...} -a mod q as 11 limbs in [0..2²⁴). +-- +local function neg(a) + return reduce(mp.sub(Q, a)) +end + +--- Subtracts scalars mod q. +-- +-- If the two operands are in Montgomery form, returns the correct result also +-- in Montgomery form, since (2²⁶⁴ × a) - (2²⁶⁴ × b) ≡ 2²⁶⁴ × (a - b) (mod q). +-- +-- @tparam {number...} a A number a < q as 11 limbs in [0..2²⁴). +-- @tparam {number...} b A number b < q as 11 limbs in [0..2²⁴). +-- @treturn {number...} a - b mod q as 11 limbs in [0..2²⁴). +-- +local function sub(a, b) + return add(a, neg(b)) +end + +--- Given two scalars a and b, computes 2⁻²⁶⁴ × a × b mod q. +-- +-- @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²⁴). +-- @treturn {number...} 2⁻²⁶⁴ × a × b mod q as 11 limbs in [0..2²⁴). +-- +local function mul(a, b) + local t0, t1 = mp.mul(a, b) + local mq0, mq1 = mp.mul(mp.lmul(t0, T0), Q) + local _, s1 = mp.dwadd(t0, t1, mq0, mq1) + return reduce(s1) +end + +--- Converts a scalar into Montgomery form. +-- +-- @tparam {number...} a A number a as 11 limbs in [0..2²⁴). +-- @treturn {number...} 2²⁶⁴ × a mod q as 11 limbs in [0..2²⁴). +-- +local function montgomery(a) + -- 0 ≤ a < 2²⁶⁴ and 0 ≤ T1 < q. + return mul(a, T1) +end + +--- Converts a scalar from Montgomery form. +-- +-- @tparam {number...} a A number a < q as 11 limbs in [0..2²⁴). +-- @treturn {number...} 2⁻²⁶⁴ × a mod q as 11 limbs in [0..2²⁴). +-- +local function demontgomery(a) + -- It's REDC all over again except b is 1. + local mq0, mq1 = mp.mul(mp.lmul(a, T0), Q) + local _, s1 = mp.dwadd(a, ZERO, mq0, mq1) + return reduce(s1) +end + +--- Encodes a scalar. +-- +-- @tparam {number...} a A number 2²⁶⁴ × a mod q as 11 limbs in [0..2²⁴). +-- @treturn string The 32-byte string encoding of a. +-- +local function encode(a) + return pfq(fmtfq, unpack(demontgomery(a))) +end + +--- Decodes a scalar. +-- +-- @tparam string str A 32-byte string encoding some little-endian number a. +-- @treturn {number...} 2²⁶⁴ × a mod q as 11 limbs in [0..2²⁴). +-- +local function decode(str) + local dec = {ufq(fmtfq, str, 1)} dec[12] = nil + return montgomery(dec) +end + +--- Decodes a scalar from a "wide" string. +-- +-- @tparam string str A 64-byte string encoding some little-endian number a. +-- @treturn {number...} 2²⁶⁴ × a mod q as 11 limbs in [0..2²⁴). +-- +local function decodeWide(str) + 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 + +--- Decodes a scalar using the X25519/Ed25519 bit clamping scheme. +-- +-- @tparam string str A 32-byte string encoding some little-endian number a. +-- @treturn {number...} 2²⁶⁴ × clamp(a) mod q as 11 limbs in [0..2²⁴). +-- +local function decodeClamped(str) + -- 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 montgomery(words) +end + +--- Divides a scalar by 8. +-- +-- @tparam {number...} 2²⁶⁴ × a 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) + return mul(a, T8) +end + +--- Returns a scalar in binary. +-- +-- @tparam {number...} a A number a < q as 11 limbs in [0..2²⁴). +-- @treturn {number...} 2⁻²⁶⁴ × a mod q as 253 bits. +-- +local function bits(a) + local out = util.rebaseLE(demontgomery(a), 2 ^ 24, 2) + for i = 254, 289 do out[i] = nil end + return out +end + +--- Makes a PRAC ruleset from a pair of scalars. +-- +-- For more information see section 3.3 of Speeding up subgroup cryptosystems: +-- Martijn Stam. Speeding up subgroup cryptosystems. PhD thesis, Technische +-- Universiteit Eindhoven, 2003. https://dx.doi.org/10.6100/IR564670. +-- +-- @tparam {number...} a A scalar 2²⁶⁴ × a mod q as 11 limbs in [0..2²⁴). +-- @tparam {number...} b A scalar 2²⁶⁴ × b mod q as 11 limbs in [0..2²⁴). +-- @treturn {{number...}, {number...}} The generated ruleset. +-- +local function makeRuleset(a, b) + -- The numbers in raw multiprecision tables. + local dt = demontgomery(a) -- (-2²⁴..2²⁴) + local et = demontgomery(b) -- (-2²⁴..2²⁴) + local ft = mp.sub(dt, et) -- (-2²⁵..2²⁵) + + -- Residue classes of (d, e) modulo 2. + local d2 = mp.mod2(dt) + local e2 = mp.mod2(et) + + -- Residue classes of (d, e) modulo 3. + local d3 = mp.mod3(dt) + local e3 = mp.mod3(et) + + -- (e, d - e) in limited-precision floating-point numbers. + local ef = mp.approx(et) + local ff = mp.approx(ft) + + -- Lookup table for inversions and halvings modulo 3. + local lut3 = {[0] = 0, 2, 1} + + local rules = {} + while ff ~= 0 do + if ff < 0 then + -- M0. d < e + rules[#rules + 1] = 0 + -- (d, e) ← (e, d) + dt, et = et, dt + d2, e2 = e2, d2 + d3, e3 = e3, d3 + ef = mp.approx(et) + ft = mp.sub(dt, et) + ff = -ff + elseif 4 * ff < ef and d3 == lut3[e3] then + -- M1. e < d ≤ 5/4 e, d ≡ -e (mod 3) + rules[#rules + 1] = 1 + -- (d, e) ← ((2d - e)/3, (2e - d)/3) + dt, et = mp.third(mp.add(dt, ft)), mp.third(mp.sub(et, ft)) + d2, e2 = e2, d2 + d3, e3 = mp.mod3(dt), mp.mod3(et) + ef = mp.approx(et) + elseif 4 * ff < ef and d2 == e2 and d3 == e3 then + -- M2. e < d ≤ 5/4 e, d ≡ e (mod 6) + rules[#rules + 1] = 2 + -- (d, e) ← ((d - e)/2, e) + dt = mp.half(ft) + d2 = mp.mod2(dt) + d3 = lut3[(d3 - e3) % 3] + ft = mp.sub(dt, et) + ff = mp.approx(ft) + elseif ff < 3 * ef then + -- M3. d ≤ 4e + rules[#rules + 1] = 3 + -- (d, e) ← (d - e, e) + dt = mp.carryWeak(ft) + d2 = (d2 - e2) % 2 + d3 = (d3 - e3) % 3 + ft = mp.sub(dt, et) + ff = mp.approx(ft) + elseif d2 == e2 then + -- M4. d ≡ e (mod 2) + rules[#rules + 1] = 2 + -- (d, e) ← ((d - e)/2, e) + dt = mp.half(ft) + d2 = mp.mod2(dt) + d3 = lut3[(d3 - e3) % 3] + ft = mp.sub(dt, et) + ff = mp.approx(ft) + elseif d2 == 0 then + -- M5. d ≡ 0 (mod 2) + rules[#rules + 1] = 5 + -- (d, e) ← (d/2, e) + dt = mp.half(dt) + d2 = mp.mod2(dt) + d3 = lut3[d3] + ft = mp.sub(dt, et) + ff = mp.approx(ft) + elseif d3 == 0 then + -- M6. d ≡ 0 (mod 3) + rules[#rules + 1] = 6 + -- (d, e) ← (d/3 - e, e) + dt = mp.carryWeak(mp.sub(mp.third(dt), et)) + d2 = (d2 - e2) % 2 + d3 = mp.mod3(dt) + ft = mp.sub(dt, et) + ff = mp.approx(ft) + elseif d3 == lut3[e3] then + -- M7. d ≡ -e (mod 3) + rules[#rules + 1] = 7 + -- (d, e) ← ((d - 2e)/3, e) + dt = mp.third(mp.sub(ft, et)) + d3 = mp.mod3(dt) + ft = mp.sub(dt, et) + ff = mp.approx(ft) + elseif d3 == e3 then + -- M8. d ≡ e (mod 3) + rules[#rules + 1] = 8 + -- (d, e) ← ((d - e)/3, e) + dt = mp.third(ft) + d2 = (d2 - e2) % 2 + d3 = mp.mod3(dt) + ft = mp.sub(dt, et) + ff = mp.approx(ft) + else + -- M9. e ≡ 0 (mod 2) + rules[#rules + 1] = 9 + -- (d, e) ← (d, e/2) + et = mp.half(et) + e2 = mp.mod2(et) + e3 = lut3[e3] + ef = mp.approx(et) + ft = mp.sub(dt, et) + ff = mp.approx(ft) + end + end + + local ubits = util.rebaseLE(dt, 2 ^ 24, 2) + while ubits[#ubits] == 0 do ubits[#ubits] = nil end + + return {ubits, rules} +end + +return { + add = add, + sub = sub, + mul = mul, + encode = encode, + decode = decode, + decodeWide = decodeWide, + decodeClamped = decodeClamped, + eighth = eighth, + bits = bits, + makeRuleset = makeRuleset, +} diff --git a/src/telem/vendor/ccryptolib/internal/mp.lua b/src/telem/vendor/ccryptolib/internal/mp.lua new file mode 100644 index 0000000..d86e557 --- /dev/null +++ b/src/telem/vendor/ccryptolib/internal/mp.lua @@ -0,0 +1,299 @@ +--- Multi-precision arithmetic on 264-bit integers. + +local unpack = unpack or table.unpack + +--- A little-endian big integer of width 11 in (-2⁵²..2⁵²). +--- @class MpSW11L52 + +--- A little-endian big integer of width 11 in (-2²⁴, 2²⁴). +--- @class MpSW11L24: MpSW11L52 + +--- A little-endian big integer of width 11 in [0..2²⁴). +--- @class MpUW11L24: MpSW11L24 + +--- Carries a number in base 2²⁴ into a signed limb form. +--- @param a MpSW11L52 +--- @return MpSW11L24 low The carried low limbs. +--- @return number carry The overflowed carry. +local function carryWeak(a) + local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a) + + local h00 = a00 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a01 = a01 + h00 * 2 ^ -24 + local h01 = a01 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a02 = a02 + h01 * 2 ^ -24 + local h02 = a02 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a03 = a03 + h02 * 2 ^ -24 + local h03 = a03 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a04 = a04 + h03 * 2 ^ -24 + local h04 = a04 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a05 = a05 + h04 * 2 ^ -24 + local h05 = a05 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a06 = a06 + h05 * 2 ^ -24 + local h06 = a06 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a07 = a07 + h06 * 2 ^ -24 + local h07 = a07 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a08 = a08 + h07 * 2 ^ -24 + local h08 = a08 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a09 = a09 + h08 * 2 ^ -24 + local h09 = a09 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a10 = a10 + h09 * 2 ^ -24 + local h10 = a10 + 3 * 2 ^ 75 - 3 * 2 ^ 75 + + return { + a00 - h00, + a01 - h01, + a02 - h02, + a03 - h03, + a04 - h04, + a05 - h05, + a06 - h06, + a07 - h07, + a08 - h08, + a09 - h09, + a10 - h10, + }, h10 * 2 ^ -24 +end + +--- Carries a number in base 2²⁴. +--- @param a MpSW11L52 +--- @return MpUW11L24 low The low 11 limbs of the output. +--- @return number carry The overflow carry. +local function carry(a) + local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a) + + local l00 = a00 % 2 ^ 24 a01 = a01 + (a00 - l00) * 2 ^ -24 + local l01 = a01 % 2 ^ 24 a02 = a02 + (a01 - l01) * 2 ^ -24 + local l02 = a02 % 2 ^ 24 a03 = a03 + (a02 - l02) * 2 ^ -24 + local l03 = a03 % 2 ^ 24 a04 = a04 + (a03 - l03) * 2 ^ -24 + local l04 = a04 % 2 ^ 24 a05 = a05 + (a04 - l04) * 2 ^ -24 + local l05 = a05 % 2 ^ 24 a06 = a06 + (a05 - l05) * 2 ^ -24 + local l06 = a06 % 2 ^ 24 a07 = a07 + (a06 - l06) * 2 ^ -24 + local l07 = a07 % 2 ^ 24 a08 = a08 + (a07 - l07) * 2 ^ -24 + local l08 = a08 % 2 ^ 24 a09 = a09 + (a08 - l08) * 2 ^ -24 + local l09 = a09 % 2 ^ 24 a10 = a10 + (a09 - l09) * 2 ^ -24 + local l10 = a10 % 2 ^ 24 + local h10 = (a10 - l10) * 2 ^ -24 + + return {l00, l01, l02, l03, l04, l05, l06, l07, l08, l09, l10}, h10 +end + +--- Adds two numbers. +--- @param a MpSW11L24 +--- @param b MpSW11L24 +--- @return MpSW11L52 c a + b +local function add(a, b) + local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a) + local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10 = unpack(b) + + return { + a00 + b00, + a01 + b01, + a02 + b02, + a03 + b03, + a04 + b04, + a05 + b05, + a06 + b06, + a07 + b07, + a08 + b08, + a09 + b09, + a10 + b10, + } +end + +--- Subtracts a number from another. +--- @param a MpSW11L24 +--- @param b MpSW11L24 +--- @return MpSW11L52 c a - b +local function sub(a, b) + local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a) + local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10 = unpack(b) + + return { + a00 - b00, + a01 - b01, + a02 - b02, + a03 - b03, + a04 - b04, + a05 - b05, + a06 - b06, + a07 - b07, + a08 - b08, + a09 - b09, + a10 - b10, + } +end + +--- Computes the lower half of a product between two numbers. +--- @param a MpUW11L24 +--- @param b MpUW11L24 +--- @return MpUW11L24 c a × b (mod 2²⁶⁴) +--- @return number carry ⌊a × b ÷ 2²⁶⁴⌋ +local function lmul(a, b) + local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a) + local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10 = unpack(b) + + return carry { + a00 * b00, + a01 * b00 + a00 * b01, + a02 * b00 + a01 * b01 + a00 * b02, + a03 * b00 + a02 * b01 + a01 * b02 + a00 * b03, + a04 * b00 + a03 * b01 + a02 * b02 + a01 * b03 + a00 * b04, + a05 * b00 + a04 * b01 + a03 * b02 + a02 * b03 + a01 * b04 + a00 * b05, + a06 * b00 + a05 * b01 + a04 * b02 + a03 * b03 + a02 * b04 + a01 * b05 + a00 * b06, + a07 * b00 + a06 * b01 + a05 * b02 + a04 * b03 + a03 * b04 + a02 * b05 + a01 * b06 + a00 * b07, + a08 * b00 + a07 * b01 + a06 * b02 + a05 * b03 + a04 * b04 + a03 * b05 + a02 * b06 + a01 * b07 + a00 * b08, + a09 * b00 + a08 * b01 + a07 * b02 + a06 * b03 + a05 * b04 + a04 * b05 + a03 * b06 + a02 * b07 + a01 * b08 + a00 * b09, + a10 * b00 + a09 * b01 + a08 * b02 + a07 * b03 + a06 * b04 + a05 * b05 + a04 * b06 + a03 * b07 + a02 * b08 + a01 * b09 + a00 * b10, + } +end + +--- Computes the a product between two numbers. +--- @param a MpUW11L24 +--- @param b MpUW11L24 +--- @return MpUW11L24 low The low 11 limbs of a × b. +--- @return MpUW11L24 high The high 11 limbs of a × b. +local function mul(a, b) + local low, of = lmul(a, b) + + local _, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a) + local _, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10 = unpack(b) + + -- The carry is always 0. + return low, (carry { + of + a10 * b01 + a09 * b02 + a08 * b03 + a07 * b04 + a06 * b05 + a05 * b06 + a04 * b07 + a03 * b08 + a02 * b09 + a01 * b10, + a10 * b02 + a09 * b03 + a08 * b04 + a07 * b05 + a06 * b06 + a05 * b07 + a04 * b08 + a03 * b09 + a02 * b10, + a10 * b03 + a09 * b04 + a08 * b05 + a07 * b06 + a06 * b07 + a05 * b08 + a04 * b09 + a03 * b10, + a10 * b04 + a09 * b05 + a08 * b06 + a07 * b07 + a06 * b08 + a05 * b09 + a04 * b10, + a10 * b05 + a09 * b06 + a08 * b07 + a07 * b08 + a06 * b09 + a05 * b10, + a10 * b06 + a09 * b07 + a08 * b08 + a07 * b09 + a06 * b10, + a10 * b07 + a09 * b08 + a08 * b09 + a07 * b10, + a10 * b08 + a09 * b09 + a08 * b10, + a10 * b09 + a09 * b10, + a10 * b10, + 0 + }) +end + +--- Computes a double-width sum of two numbers. +--- @param a0 MpUW11L24 The low 11 limbs of a. +--- @param a1 MpUW11L24 The high 11 limbs of a. +--- @param b0 MpUW11L24 The low 11 limbs of b. +--- @param b1 MpUW11L24 The high 11 limbs of b. +--- @return MpUW11L24 c0 The low 11 limbs of a + b. +--- @return MpUW11L24 c1 The high 11 limbs of a + b. +--- @return number The carry. +local function dwadd(a0, a1, b0, b1) + local low, c = carry(add(a0, b0)) + local high = add(a1, b1) + high[1] = high[1] + c + return low, carry(high) +end + +--- Computes half of a number. +--- @param a MpSW11L24 The number to halve, must be even. +--- @return MpSW11L24 c a ÷ 2 +local function half(a) + local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a) + + return (carryWeak { + a00 * 0.5 + a01 * 2 ^ 23, + a02 * 2 ^ 23, + a03 * 2 ^ 23, + a04 * 2 ^ 23, + a05 * 2 ^ 23, + a06 * 2 ^ 23, + a07 * 2 ^ 23, + a08 * 2 ^ 23, + a09 * 2 ^ 23, + a10 * 2 ^ 23, + 0, + }) +end + +--- Computes a third of a number. +--- @param a MpSW11L24 The number to divide, must be a multiple of 3. +--- @return MpSW11L24 c a ÷ 3 +local function third(a) + local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a) + + local d00 = a00 * 0xaaaaaa + local d01 = a01 * 0xaaaaaa + d00 + local d02 = a02 * 0xaaaaaa + d01 + local d03 = a03 * 0xaaaaaa + d02 + local d04 = a04 * 0xaaaaaa + d03 + local d05 = a05 * 0xaaaaaa + d04 + local d06 = a06 * 0xaaaaaa + d05 + local d07 = a07 * 0xaaaaaa + d06 + local d08 = a08 * 0xaaaaaa + d07 + local d09 = a09 * 0xaaaaaa + d08 + local d10 = a10 * 0xaaaaaa + d09 + + -- We compute the modular division mod 2²⁶⁴. The carry isn't 0 but it isn't + -- part of a ÷ 3 either. + return (carryWeak { + a00 + d00, + a01 + d01, + a02 + d02, + a03 + d03, + a04 + d04, + a05 + d05, + a06 + d06, + a07 + d07, + a08 + d08, + a09 + d09, + a10 + d10, + }) +end + +--- Computes a number modulo 2. +--- @param a MpSW11L24 +--- @return number c a mod 2. +local function mod2(a) + return a[1] % 2 +end + +--- Computes a number modulo 3. +--- @param a MpSW11L24 +--- @return number c a mod 3. +local function mod3(a) + local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a) + return (a00 + a01 + a02 + a03 + a04 + a05 + a06 + a07 + a08 + a09 + a10) % 3 +end + +--- Computes a double representing the most-significant bits of a number. +--- @param a MpSW11L52 +--- @return number c A floating-point approximation for the value of a. +local function approx(a) + local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a) + return a00 + + a01 * 2 ^ 24 + + a02 * 2 ^ 48 + + a03 * 2 ^ 72 + + a04 * 2 ^ 96 + + a05 * 2 ^ 120 + + a06 * 2 ^ 144 + + a07 * 2 ^ 168 + + a08 * 2 ^ 192 + + a09 * 2 ^ 216 + + a10 * 2 ^ 240 +end + +--- Compares two numbers for ordering. +--- @param a MpSW11L24 +--- @param b MpSW11L24 +--- @return number ord Some number with ord < 0 iff a < b and ord = 0 iff a = b. +local function cmp(a, b) + return approx(sub(a, b)) +end + +local function num(a) + return {a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +end + +return { + carry = carry, + carryWeak = carryWeak, + add = add, + sub = sub, + dwadd = dwadd, + lmul = lmul, + mul = mul, + half = half, + third = third, + mod2 = mod2, + mod3 = mod3, + approx = approx, + cmp = cmp, + num = num, +} diff --git a/src/telem/vendor/ccryptolib/internal/packing.lua b/src/telem/vendor/ccryptolib/internal/packing.lua new file mode 100644 index 0000000..1ea0db3 --- /dev/null +++ b/src/telem/vendor/ccryptolib/internal/packing.lua @@ -0,0 +1,161 @@ +--- High-performance binary packing of integers. +--- +--- Remark (and 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. + +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 + +-- 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 <= 16, "integral size out of limits") + w[#w + 1] = n + end + return fn(w, e == ">") + end + + local packCache = {} + local unpackCache = {} + + -- I CAN'T EVEN WITH THIS EXTENSION, WHY CAN'T IT HANDLE MORE THAN A SINGLE + -- LINE OF RETURN DESCRIPTION? LOOK AT IT!!! THE COMMENT GOES OVER THERE ------------------------------------------------------------------> look! ↓ ↓ ↓ + + --- (string.pack is nil) Compiles a binary packing function. + --- + --- Errors if the format string is invalid or has an invalid integral size, + --- or if the compiled function turns out too large. + --- + --- @param fmt string A string matched by `^([><])I[I%d]+$`. + --- @return fun(_ignored: any, ...: any): string pack A function that behaves like an unsafe version of `string.pack` for the given format string. + --- @return string fmt + local function compilePack(fmt) + if not packCache[fmt] then + packCache[fmt] = compile(fmt, mkPack) + end + return packCache[fmt], fmt + end + + --- (string.pack is nil) Compiles a binary unpacking function. + --- + --- Errors if the format string is invalid or has an invalid integral size, + --- or if the compiled function turns out too large. + --- + --- @param fmt string A string matched by `^([><])I[I%d]+$`. + --- @return fun(_ignored: any, str: string, pos: number) unpack A function that behaves like an unsafe version of `string.unpack` for the given format string. Note that the third argument isn't optional. + --- @return string fmt + local function compileUnpack(fmt) + if not unpackCache[fmt] then + unpackCache[fmt] = compile(fmt, mkUnpack) + end + return unpackCache[fmt], fmt + end + + return { + compilePack = compilePack, + compileUnpack = compileUnpack, + } +else + --- (string.pack isn't nil) It's string.pack! It returns string.pack! + --- @param fmt string + --- @return fun(fmt: string, ...: any): string pack string.pack! + --- @return string fmt + local function compilePack(fmt) return string.pack, fmt end + + --- (string.pack isn't nil) It's string.unpack! It returns string.unpack! + --- @param fmt string + --- @return fun(fmt: string, str: string, pos: number) unpack string.unpack! + --- @return string fmt + local function compileUnpack(fmt) return string.unpack, fmt end + + return { + compilePack = compilePack, + compileUnpack = compileUnpack, + } +end diff --git a/src/telem/vendor/ccryptolib/internal/sha512.lua b/src/telem/vendor/ccryptolib/internal/sha512.lua new file mode 100644 index 0000000..9a4eb33 --- /dev/null +++ b/src/telem/vendor/ccryptolib/internal/sha512.lua @@ -0,0 +1,169 @@ +--- The SHA512 cryptographic hash function. + +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 + a1 = a1 + (a0 - r0) / 2 ^ 32 + return a1 % 2 ^ 32, r0 +end + +local K = { + 0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, 0xb5c0fbcf, 0xec4d3b2f, + 0xe9b5dba5, 0x8189dbbc, 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019, + 0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118, 0xd807aa98, 0xa3030242, + 0x12835b01, 0x45706fbe, 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2, + 0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1, 0x9bdc06a7, 0x25c71235, + 0xc19bf174, 0xcf692694, 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3, + 0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65, 0x2de92c6f, 0x592b0275, + 0x4a7484aa, 0x6ea6e483, 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5, + 0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, 0xb00327c8, 0x98fb213f, + 0xbf597fc7, 0xbeef0ee4, 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725, + 0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70, 0x27b70a85, 0x46d22ffc, + 0x2e1b2138, 0x5c26c926, 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df, + 0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, 0x81c2c92e, 0x47edaee6, + 0x92722c85, 0x1482353b, 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001, + 0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30, 0xd192e819, 0xd6ef5218, + 0xd6990624, 0x5565a910, 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8, + 0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53, 0x2748774c, 0xdf8eeb99, + 0x34b0bcb5, 0xe19b48a8, 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb, + 0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3, 0x748f82ee, 0x5defb2fc, + 0x78a5636f, 0x43172f60, 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec, + 0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, 0xbef9a3f7, 0xb2c67915, + 0xc67178f2, 0xe372532b, 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207, + 0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178, 0x06f067aa, 0x72176fba, + 0x0a637dc5, 0xa2c898a6, 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b, + 0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, 0x3c9ebe0a, 0x15c9bebc, + 0x431d67c4, 0x9c100d4c, 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a, + 0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817, +} + +--- Hashes data bytes using SHA512. +--- @param data string The input data. +--- @return string hash The 64-byte hash value. +local function digest(data) + expect(1, data, "string") + + -- Pad input. + local bitlen = #data * 8 + local padlen = -(#data + 17) % 128 + data = data .. "\x80" .. ("\0"):rep(padlen) .. p1x16(fmt1x16, bitlen) + + -- Initialize state. + local h01, h00 = 0x6a09e667, 0xf3bcc908 + local h11, h10 = 0xbb67ae85, 0x84caa73b + local h21, h20 = 0x3c6ef372, 0xfe94f82b + local h31, h30 = 0xa54ff53a, 0x5f1d36f1 + local h41, h40 = 0x510e527f, 0xade682d1 + local h51, h50 = 0x9b05688c, 0x2b3e6c1f + local h61, h60 = 0x1f83d9ab, 0xfb41bd6b + local h71, h70 = 0x5be0cd19, 0x137e2179 + + -- Digest. + for i = 1, #data, 128 do + local w = {u32x4(fmt32x4, data, i)} + + -- Message schedule. + for j = 33, 160, 2 do + local wf1, wf0 = w[j - 30], w[j - 29] + local t1 = shr(wf1, 1) + shl(wf0, 31) + local t0 = shr(wf0, 1) + shl(wf1, 31) + local u1 = shr(wf1, 8) + shl(wf0, 24) + local u0 = shr(wf0, 8) + shl(wf1, 24) + local v1 = shr(wf1, 7) + local v0 = shr(wf0, 7) + shl(wf1, 25) + + local w21, w20 = w[j - 4], w[j - 3] + local w1 = shr(w21, 19) + shl(w20, 13) + local w0 = shr(w20, 19) + shl(w21, 13) + local x0 = shr(w21, 29) + shl(w20, 3) + local x1 = shr(w20, 29) + shl(w21, 3) + local y1 = shr(w21, 6) + local y0 = shr(w20, 6) + shl(w21, 26) + + local r1, r0 = + w[j - 32] + bxor(t1, u1, v1) + w[j - 14] + bxor(w1, x1, y1), + w[j - 31] + bxor(t0, u0, v0) + w[j - 13] + bxor(w0, x0, y0) + + w[j], w[j + 1] = carry64(r1, r0) + end + + -- Block function. + local a1, a0 = h01, h00 + local b1, b0 = h11, h10 + local c1, c0 = h21, h20 + local d1, d0 = h31, h30 + local e1, e0 = h41, h40 + local f1, f0 = h51, h50 + local g1, g0 = h61, h60 + local h1, h0 = h71, h70 + for j = 1, 160, 2 do + local t1 = shr(e1, 14) + shl(e0, 18) + local t0 = shr(e0, 14) + shl(e1, 18) + local u1 = shr(e1, 18) + shl(e0, 14) + local u0 = shr(e0, 18) + shl(e1, 14) + local v0 = shr(e1, 9) + shl(e0, 23) + local v1 = shr(e0, 9) + shl(e1, 23) + local s11 = bxor(t1, u1, v1) + local s10 = bxor(t0, u0, v0) + + local ch1 = bxor(band(e1, f1), band(bnot(e1), g1)) + local ch0 = bxor(band(e0, f0), band(bnot(e0), g0)) + + local temp11 = h1 + s11 + ch1 + K[j] + w[j] + local temp10 = h0 + s10 + ch0 + K[j + 1] + w[j + 1] + + local w1 = shr(a1, 28) + shl(a0, 4) + local w0 = shr(a0, 28) + shl(a1, 4) + local x0 = shr(a1, 2) + shl(a0, 30) + local x1 = shr(a0, 2) + shl(a1, 30) + local y0 = shr(a1, 7) + shl(a0, 25) + local y1 = shr(a0, 7) + shl(a1, 25) + local s01 = bxor(w1, x1, y1) + local s00 = bxor(w0, x0, y0) + + local maj1 = bxor(band(a1, b1), band(a1, c1), band(b1, c1)) + local maj0 = bxor(band(a0, b0), band(a0, c0), band(b0, c0)) + + local temp21 = s01 + maj1 + local temp20 = s00 + maj0 + + h1 = g1 h0 = g0 + g1 = f1 g0 = f0 + f1 = e1 f0 = e0 + e1, e0 = carry64(d1 + temp11, d0 + temp10) + d1 = c1 d0 = c0 + c1 = b1 c0 = b0 + b1 = a1 b0 = a0 + a1, a0 = carry64(temp11 + temp21, temp10 + temp20) + end + + h01, h00 = carry64(h01 + a1, h00 + a0) + h11, h10 = carry64(h11 + b1, h10 + b0) + h21, h20 = carry64(h21 + c1, h20 + c0) + h31, h30 = carry64(h31 + d1, h30 + d0) + h41, h40 = carry64(h41 + e1, h40 + e0) + h51, h50 = carry64(h51 + f1, h50 + f0) + h61, h60 = carry64(h61 + g1, h60 + g0) + h71, h70 = carry64(h71 + h1, h70 + h0) + end + + return p16x4(fmt16x4, + h01, h00, h11, h10, h21, h20, h31, h30, + h41, h40, h51, h50, h61, h60, h71, h70 + ) +end + +return { + digest = digest, +} diff --git a/src/telem/vendor/ccryptolib/internal/util.lua b/src/telem/vendor/ccryptolib/internal/util.lua new file mode 100644 index 0000000..2272f90 --- /dev/null +++ b/src/telem/vendor/ccryptolib/internal/util.lua @@ -0,0 +1,71 @@ +local function lassert(val, err, level) + if not val then error(err, level + 1) end + return val +end + +--- Converts a little-endian array from one power-of-two base to another. +--- @param a number[] The array to convert, in little-endian. +--- @param base1 number The base to convert from. Must be a power of 2. +--- @param base2 number The base to convert to. Must be a power of 2. +--- @return number[] +local function rebaseLE(a, base1, base2) -- TODO Write contract properly. + local out = {} + local outlen = 1 + local acc = 0 + local mul = 1 + for i = 1, #a do + acc = acc + a[i] * mul + mul = mul * base1 + while mul >= base2 do + local rem = acc % base2 + acc = (acc - rem) / base2 + mul = mul / base2 + out[outlen] = rem + outlen = outlen + 1 + end + end + if mul > 0 then + out[outlen] = acc + end + return out +end + +--- Decodes bits with X25519/Ed25519 exponent clamping. +--- @param str string The 32-byte encoded exponent. +--- @return number[] bits The decoded clamped bits. +local function bits(str) + -- Decode. + local bytes = {str:byte(1, 32)} + local out = {} + for i = 1, 32 do + local byte = bytes[i] + for j = -7, 0 do + local bit = byte % 2 + out[8 * i + j] = bit + byte = (byte - bit) / 2 + end + end + + -- Clamp. + out[1] = 0 + out[2] = 0 + out[3] = 0 + out[255] = 1 + out[256] = 0 + + return out +end + +--- Decodes bits with X25519/Ed25519 exponent clamping and division by 8. +--- @param str string The 32-byte encoded exponent. +--- @return number[] bits The decoded clamped bits, divided by 8. +local function bits8(str) + return {unpack(bits(str), 4)} +end + +return { + lassert = lassert, + rebaseLE = rebaseLE, + bits = bits, + bits8 = bits8, +} diff --git a/src/telem/vendor/ccryptolib/poly1305.lua b/src/telem/vendor/ccryptolib/poly1305.lua new file mode 100644 index 0000000..123aad4 --- /dev/null +++ b/src/telem/vendor/ccryptolib/poly1305.lua @@ -0,0 +1,133 @@ +--- The Poly1305 one-time authenticator. + +local expect = require "cc.expect".expect +local lassert = require "ccryptolib.internal.util".lassert +local packing = require "ccryptolib.internal.packing" + +local u4x4, fmt4x4 = packing.compileUnpack("= 0xfffb + then + c7, c6, c5, c4, c3, c2, c1, c0 = 0, 0, 0, 0, 0, 0, 0, c0 - 0xfffb + end + + -- Decode s. + local s0, s1, s2, s3 = u4x4(fmt4x4, key, 17) + + -- Add. + local t0 = s0 + c0 + c1 local u0 = t0 % 2 ^ 32 + local t1 = t0 - u0 + s1 * 2 ^ 32 + c2 + c3 local u1 = t1 % 2 ^ 64 + local t2 = t1 - u1 + s2 * 2 ^ 64 + c4 + c5 local u2 = t2 % 2 ^ 96 + local t3 = t2 - u2 + s3 * 2 ^ 96 + c6 + c7 local u3 = t3 % 2 ^ 128 + + -- Encode. + return p4x4(fmt4x4, u0, u1 / 2 ^ 32, u2 / 2 ^ 64, u3 / 2 ^ 96) +end + +return { + mac = mac, +} diff --git a/src/telem/vendor/ccryptolib/random.lua b/src/telem/vendor/ccryptolib/random.lua new file mode 100644 index 0000000..f667ad1 --- /dev/null +++ b/src/telem/vendor/ccryptolib/random.lua @@ -0,0 +1,54 @@ +local expect = require "cc.expect".expect +local blake3 = require "ccryptolib.blake3" +local chacha20 = require "ccryptolib.chacha20" +local util = require "ccryptolib.internal.util" + +local lassert = util.lassert + +-- Extract local context. +local ctx = { + "ccryptolib 2023-04-11T19:43Z random.lua initialization context", + os.epoch("utc"), + os.epoch("ingame"), + math.random(0, 2 ^ 24 - 1), + math.random(0, 2 ^ 24 - 1), + tostring({}), + tostring({}), +} + +local state = blake3.digest(table.concat(ctx, "|")) +local initialized = false + +--- Mixes entropy into the generator, and marks it as initialized. +--- @param seed string The seed data. +local function init(seed) + expect(1, seed, "string") + state = blake3.digestKeyed(state, seed) + initialized = true +end + +--- Mixes extra entropy into the generator state. +--- @param data string The additional entropy to mix. +local function mix(data) + expect(1, data, "string") + state = blake3.digestKeyed(state, data) +end + +--- Generates random bytes. +--- @param len number The desired output length. +--- @return string bytes +local function random(len) + expect(1, len, "number") + lassert(initialized, "attempt to use an uninitialized random generator", 2) + local msg = ("\0"):rep(math.max(len, 0) + 32) + local nonce = ("\0"):rep(12) + local out = chacha20.crypt(state, nonce, msg, 8, 0) + state = out:sub(1, 32) + return out:sub(33) +end + +return { + init = init, + mix = mix, + random = random, +} diff --git a/src/telem/vendor/ccryptolib/sha256.lua b/src/telem/vendor/ccryptolib/sha256.lua new file mode 100644 index 0000000..eca0f97 --- /dev/null +++ b/src/telem/vendor/ccryptolib/sha256.lua @@ -0,0 +1,156 @@ +--- The SHA256 cryptographic hash function. + +local expect = require "cc.expect".expect +local lassert = require "ccryptolib.internal.util".lassert +local packing = require "ccryptolib.internal.packing" + +local rol = bit32.lrotate +local shr = bit32.rshift +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 = {} + local p = 2 + for i = 1, n do + out[i] = bxor(p ^ exp % 1 * 2 ^ 32) + repeat p = p + 1 until 2 ^ p % p == 2 + end + return out +end + +local K = primes(64, 1 / 3) + +local h0 = primes(8, 1 / 2) + +local function compress(h, w) + local h0, h1, h2, h3, h4, h5, h6, h7 = unpack(h) + local K = K + + -- Message schedule. + for j = 17, 64 do + local wf = w[j - 15] + local w2 = w[j - 2] + local s0 = bxor(rol(wf, 25), rol(wf, 14), shr(wf, 3)) + local s1 = bxor(rol(w2, 15), rol(w2, 13), shr(w2, 10)) + w[j] = w[j - 16] + s0 + w[j - 7] + s1 + end + + -- Block. + local a, b, c, d, e, f, g, h = h0, h1, h2, h3, h4, h5, h6, h7 + for j = 1, 64 do + local s1 = bxor(rol(e, 26), rol(e, 21), rol(e, 7)) + local ch = bxor(band(e, f), band(bnot(e), g)) + local temp1 = h + s1 + ch + K[j] + w[j] + local s0 = bxor(rol(a, 30), rol(a, 19), rol(a, 10)) + local maj = bxor(band(a, b), band(a, c), band(b, c)) + local temp2 = s0 + maj + + h = g + g = f + f = e + e = d + temp1 + d = c + c = b + b = a + a = temp1 + temp2 + end + + return { + (h0 + a) % 2 ^ 32, + (h1 + b) % 2 ^ 32, + (h2 + c) % 2 ^ 32, + (h3 + d) % 2 ^ 32, + (h4 + e) % 2 ^ 32, + (h5 + f) % 2 ^ 32, + (h6 + g) % 2 ^ 32, + (h7 + h) % 2 ^ 32, + } +end + +--- Hashes data using SHA256. +--- @param data string Input bytes. +--- @return string hash The 32-byte hash value. +local function digest(data) + expect(1, data, "string") + + -- Pad input. + local bitlen = #data * 8 + local padlen = -(#data + 9) % 64 + data = data .. "\x80" .. ("\0"):rep(padlen) .. p1x8(fmt1x8, bitlen) + + -- Digest. + local h = h0 + for i = 1, #data, 64 do + h = compress(h, {u16x4(fmt16x4, data, i)}) + end + + return p8x4(fmt8x4, unpack(h)) +end + +-- Reports once every ~10ms on a standard CCEmuX emulator. +local PBKDF2_CB_ITERATIONS = 50 + +--- Hashes a password using PBKDF2-HMAC-SHA256. +--- @param password string The password to hash. +--- @param salt string The password's salt. +--- @param iter number The number of iterations to perform. +--- @param progress fun(iter: number)? An optional function to periodically call with the current iteration number as argument. +--- @return string dk The 32-byte derived key. +local function pbkdf2(password, salt, iter, progress) + expect(1, password, "string") + expect(2, salt, "string") + expect(3, iter, "number") + lassert(iter % 1 == 0, "iteration number must be an integer", 2) + lassert(iter > 0, "iteration number must be positive", 2) + expect(4, progress, "function", "nil") + + -- Pad password. + if #password > 64 then password = digest(password) end + password = {u16x4(fmt16x4, password .. ("\0"):rep(64), 1)} + + -- Compute password blocks. + local ikp = {} + local okp = {} + for i = 1, 16 do + ikp[i] = bxor(password[i], 0x36363636) + okp[i] = bxor(password[i], 0x5c5c5c5c) + end + + local hikp = compress(h0, ikp) + local hokp = compress(h0, okp) + + -- 96-byte padding. + local pad96 = {2 ^ 31, 0, 0, 0, 0, 0, 0, 0x300} + + -- First iteration. + 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) + + -- Second iteration onwards. + local out = {unpack(hs)} + for i = 2, iter do + for j = 1, 8 do hs[j + 8] = pad96[j] end + hs = compress(hikp, hs) + for j = 1, 8 do hs[j + 8] = pad96[j] end + hs = compress(hokp, hs) + for j = 1, 8 do out[j] = bxor(out[j], hs[j]) end + if progress and i % PBKDF2_CB_ITERATIONS == 0 then progress(i) end + end + + return p8x4(fmt8x4, unpack(out)) +end + +return { + digest = digest, + pbkdf2 = pbkdf2, +} diff --git a/src/telem/vendor/ccryptolib/util.lua b/src/telem/vendor/ccryptolib/util.lua new file mode 100644 index 0000000..c4bf192 --- /dev/null +++ b/src/telem/vendor/ccryptolib/util.lua @@ -0,0 +1,45 @@ +--- General utilities for handling byte strings. + +local expect = require "cc.expect".expect +local random = require "ccryptolib.random" +local poly1305 = require "ccryptolib.poly1305" + +--- Returns the hexadecimal version of a string. +--- @param str string A string. +--- @return string hex The hexadecimal version of the string. +local function toHex(str) + expect(1, str, "string") + return ("%02x"):rep(#str):format(str:byte(1, -1)) +end + +--- Converts back a string from hexadecimal. +--- @param hex string A hexadecimal string. +--- @return string? str The original string, or nil if the input is invalid. +local function fromHex(hex) + expect(1, hex, "string") + local out = {} + local n = 0 + for c in hex:gmatch("%x%x") do + n = n + 1 + out[n] = tonumber(c, 16) + end + if 2 * n == #hex then return string.char(table.unpack(out)) end +end + +--- Compares two strings while mitigating secret leakage through timing. +--- @param a string +--- @param b string +--- @return boolean eq Whether a == b. +local function compare(a, b) + expect(1, a, "string") + expect(2, b, "string") + if #a ~= #b then return false end + local kaux = random.random(32) + return poly1305.mac(kaux, a) == poly1305.mac(kaux, b) +end + +return { + toHex = toHex, + fromHex = fromHex, + compare = compare, +} diff --git a/src/telem/vendor/ccryptolib/x25519.lua b/src/telem/vendor/ccryptolib/x25519.lua new file mode 100644 index 0000000..0cd5751 --- /dev/null +++ b/src/telem/vendor/ccryptolib/x25519.lua @@ -0,0 +1,32 @@ +--- The X25519 key exchange scheme. + +local expect = require "cc.expect".expect +local lassert = require "ccryptolib.internal.util".lassert +local util = require "ccryptolib.internal.util" +local c25 = require "ccryptolib.internal.curve25519" + +--- Computes the public key from a secret key. +--- @param sk string A random 32-byte secret key. +--- @return string pk The matching public key. +local function publicKey(sk) + expect(1, sk, "string") + assert(#sk == 32, "secret key length must be 32") + return c25.encode(c25.scale(c25.mulG(util.bits(sk)))) +end + +--- Performs the key exchange. +--- @param sk string A Curve25519 secret key. +--- @param pk string A public key, usually derived from someone else's secret key. +--- @return string ss The 32-byte shared secret between both keys. +local function exchange(sk, pk) + expect(1, sk, "string") + lassert(#sk == 32, "secret key length must be 32", 2) + expect(2, pk, "string") + lassert(#pk == 32, "public key length must be 32", 2) --- @cast pk String32 + return c25.encode(c25.scale(c25.ladder8(c25.decode(pk), util.bits8(sk)))) +end + +return { + publicKey = publicKey, + exchange = exchange, +} diff --git a/src/telem/vendor/ccryptolib/x25519c.lua b/src/telem/vendor/ccryptolib/x25519c.lua new file mode 100644 index 0000000..391d1f3 --- /dev/null +++ b/src/telem/vendor/ccryptolib/x25519c.lua @@ -0,0 +1,150 @@ +local expect = require "cc.expect".expect +local lassert = require "ccryptolib.internal.util".lassert +local fq = require "ccryptolib.internal.fq" +local fp = require "ccryptolib.internal.fp" +local c25 = require "ccryptolib.internal.curve25519" +local sha512 = require "ccryptolib.internal.sha512" +local random = require "ccryptolib.random" + +--- Masks an exchange secret key. +--- @param sk string A random 32-byte Curve25519 secret key. +--- @return string msk A masked secret key. +local function mask(sk) + expect(1, sk, "string") + lassert(#sk == 32, "secret key length must be 32", 2) + local mask = random.random(32) + local x = fq.decodeClamped(sk) + local r = fq.decodeClamped(mask) + local xr = fq.sub(x, r) + return fq.encode(xr) .. mask +end + +--- Masks a signature secret key. +--- @param sk string A random 32-byte Edwards25519 secret key. +--- @return string msk A masked secret key. +function maskS(sk) + expect(1, sk, "string") + lassert(#sk == 32, "secret key length must be 32", 2) + return mask(sha512.digest(sk):sub(1, 32)) +end + +--- Rerandomizes the masking on a masked key. +--- @param msk string A masked secret key. +--- @return string msk The same secret key, but with another mask. +local function remask(msk) + expect(1, msk, "string") + lassert(#msk == 64, "masked secret key length must be 64", 2) + local newMask = random.random(32) + local xr = fq.decode(msk:sub(1, 32)) + local r = fq.decodeClamped(msk:sub(33)) + local s = fq.decodeClamped(newMask) + local xs = fq.add(xr, fq.sub(r, s)) + return fq.encode(xs) .. newMask +end + +--- Returns the ephemeral exchange secret key of this masked key. +--- This is the second secret key in the "double key exchange" in @{exchange}, +--- the first being the key that has been masked. The ephemeral key changes +--- every time @{remask} is called. +--- @param msk string A masked secret key. +--- @return string esk The ephemeral half of the masked secret key. +local function ephemeralSk(msk) + expect(1, msk, "string") + lassert(#msk == 64, "masked secret key length must be 64", 2) + return msk: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) + if not rP then + local out = fp.encode(fp.num(0)) + return out, out + end + + local xP = c25.dadd(dP, rP, xrP) + + -- Extract coordinates for scaling. + local Px, Pz = P[1], P[2] + local xPx, xPz = xP[1], xP[2] + local rPx, rPz = rP[1], rP[2] + + -- Ensure all Z coordinates are squares. + Px, Pz = fp.mul(Px, Pz), fp.square(Pz) + xPx, xPz = fp.mul(xPx, xPz), fp.square(xPz) + rPx, rPz = fp.mul(rPx, rPz), fp.square(rPz) + + -- We're splitting the secret x into (x - r (mod q), r). The multiplication + -- adds them back together, but this only works if P's order is q, which is + -- not the case on the twist. + -- As a result, we need to check if P is on the twist and return 0 so as to + -- not leak part of x. We do this by checking the curve equation against P. + -- The projective equation for curve25519 is Y²Z = X(X² + AXZ + Z²). Since Z + -- is a square, checking validity means checking the right-hand side to be a + -- square. + local Px2 = fp.square(Px) + local Pz2 = fp.square(Pz) + local Pxz = fp.mul(Px, Pz) + local APxz = fp.kmul(Pxz, 486662) + local rhs = fp.mul(Px, fp.add(Px2, fp.carry(fp.add(APxz, Pz2)))) + + -- Find the square root of 1 / (rhs * xPz * rPz). + -- Neither rPz, xPz, nor rhs are 0: + -- - If rhs was 0, then P would be low order, which would return at (1). + -- - Since P isn't low order, clamping prevents the ladder from returning O. + -- Since we've just squared both xPz and rPz, the root will exist iff rhs is + -- a square. This checks the curve equation, so we're done. + local root = fp.sqrtDiv(fp.num(1), fp.mul(fp.mul(xPz, rPz), rhs)) + if not root then + local out = fp.encode(fp.num(0)) + return out, out + end + + -- Get the inverses of both Z values. + local xPzrPzInv = fp.mul(fp.square(root), rhs) + local xPzInv = fp.mul(xPzrPzInv, rPz) + local rPzInv = fp.mul(xPzrPzInv, xPz) + + -- Finish scaling and encode the output. + return fp.encode(fp.mul(xPx, xPzInv)), fp.encode(fp.mul(rPx, rPzInv)) +end + +--- Returns the X25519 public key of this masked key. +--- @param msk string A masked secret key. +local function publicKey(msk) + expect(1, msk, "string") + lassert(#msk == 64, "masked secret key length must be 64", 2) + return (exchangeOnPoint(msk, c25.G)) +end + +--- Performs a double key exchange. +--- +--- 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 +--- even on the twist. +--- +--- 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. +--- +--- @param sk string A masked secret key. +--- @param pk string An X25519 public key. +--- @return string sss The shared secret between the public key and the static half of the masked key. +--- @return string sse The shared secret betwen the public key and the ephemeral half of the masked key. +local function exchange(sk, pk) + expect(1, sk, "string") + lassert(#sk == 64, "masked secret key length must be 64", 2) + expect(2, pk, "string") + lassert(#pk == 32, "public key length must be 32", 2) --- @cast pk String32 + return exchangeOnPoint(sk, c25.decode(pk)) +end + +return { + mask = mask, + remask = remask, + publicKey = publicKey, + ephemeralSk = ephemeralSk, + exchange = exchange, +} diff --git a/src/telem/vendor/ecnet.lua b/src/telem/vendor/ecnet.lua deleted file mode 100644 index 09ec8f6..0000000 --- a/src/telem/vendor/ecnet.lua +++ /dev/null @@ -1,632 +0,0 @@ -local e={}local t,a,o=require,{},{startup=e} -local function i(n)local s=o[n] -if s~=nil then if s==e then -error("loop or previous error loading module '"..n.. -"'",2)end;return s end;o[n]=e;local h=a[n]if h then s=h(n)elseif t then s=t(n)else -error("cannot load '"..n.."'",2)end;if s==nil then s=true end;o[n]=s;return s end -a["ecnet.util"]=function(...) -local n={__tostring=function(r)return string.char(unpack(r))end,__index={toHex=function(r)return("%02x"):rep( -#r):format(unpack(r))end,isEqual=function(r,d)if -type(d)~="table"then return false end;if#r~=#d then return false end;local l=0 -for u=1, -#r do l=bit32.bor(l,bit32.bxor(r[u],d[u]))end;return l==0 end}} -local function s(r,d)local l=fs.open(r,"wb")l.write(d)l.close()end -local function h(r)local d=fs.open(r,"rb")local l=d.readAll()d.close()return l end;return{byteTableMT=n,saveFile=s,loadFile=h}end -a["ecnet.symmetric.siphash"]=function(...)local n=i("ecnet.util")local s=bit32.bxor -local h=bit32.band;local r=bit32.rshift;local d=n.byteTableMT;local function l(f)local m=#f -while(#f%8 ~=7)do f[#f+1]=0 end;f[#f+1]=m%256 end -local function u(m,f,w,y,p,v,b,g)local k,q;m=m+w;f=(f+y+ (m> -0xffffffff and 1 or 0))% -0x100000000;m=m%0x100000000;p=p+b;v= -( -v+g+ (p>0xffffffff and 1 or 0))%0x100000000;p=p%0x100000000;k= -h(w,0xfff80000)/0x80000;q=h(y,0xfff80000)/0x80000;w= -w*0x2000+q;y=y*0x2000+k;k=h(b,0xffff0000)/0x10000;q= -h(g,0xffff0000)/0x10000;b=b*0x10000+q;g=g*0x10000+k -w=s(w,m)y=s(y,f)b=s(b,p)g=s(g,v)m,f=f,m;p=p+w -v=(v+y+ -(p>0xffffffff and 1 or 0))%0x100000000;p=p%0x100000000;m=m+b;f= -(f+g+ (m>0xffffffff and 1 or 0))%0x100000000;m=m%0x100000000;k= -h(w,0xffff8000)/0x8000;q=h(y,0xffff8000)/0x8000;w= -w*0x20000+q;y=y*0x20000+k;k=h(b,0xfffff800)/0x800;q= -h(g,0xfffff800)/0x800;b=b*0x200000+q;g=g*0x200000+k -w=s(w,p)y=s(y,v)b=s(b,m)g=s(g,f)p,v=v,p;return m,f,w,y,p,v,b,g end -local function c(m,f) -local w=type(f)=="table"and -{string.char(unpack(f)):byte(1,-1)}or{tostring(f):byte(1,-1)} -local y=type(m)=="table"and -{string.char(unpack(m)):byte(1,-1)}or{tostring(m):byte(1,-1)} -assert(#w==16,"SipHash: Invalid key length ("..#w.."), must be 16")l(y) -local p=s(w[1],w[2]*0x100,w[3]*0x10000,w[4]*0x1000000,0x70736575) -local v=s(w[5],w[6]*0x100,w[7]*0x10000,w[8]*0x1000000,0x736f6d65) -local b=s(w[9],w[10]*0x100,w[11]*0x10000,w[12]*0x1000000,0x6e646f6d) -local g=s(w[13],w[14]*0x100,w[15]*0x10000,w[16]*0x1000000,0x646f7261) -local k=s(w[1],w[2]*0x100,w[3]*0x10000,w[4]*0x1000000,0x6e657261) -local q=s(w[5],w[6]*0x100,w[7]*0x10000,w[8]*0x1000000,0x6c796765) -local j=s(w[9],w[10]*0x100,w[11]*0x10000,w[12]*0x1000000,0x79746573) -local x=s(w[13],w[14]*0x100,w[15]*0x10000,w[16]*0x1000000,0x74656462) -for T=1,#y,8 do -j=s(y[T],y[T+1]*0x100,y[T+2]*0x10000,y[T+3]*0x1000000,j) -x=s(y[T+4],y[T+5]*0x100,y[T+6]*0x10000,y[T+7]*0x1000000,x)p,v,b,g,k,q,j,x=u(p,v,b,g,k,q,j,x)p,v,b,g,k,q,j,x=u(p,v,b,g,k,q,j,x) -p=s(y[T], -y[T+1]*0x100,y[T+2]*0x10000,y[T+3]*0x1000000,p) -v=s(y[T+4],y[T+5]*0x100,y[T+6]*0x10000,y[T+7]*0x1000000,v) -if T%64000 ==0 then os.queueEvent("")os.pullEvent("")end end;k=s(k,0xff)p,v,b,g,k,q,j,x=u(p,v,b,g,k,q,j,x) -p,v,b,g,k,q,j,x=u(p,v,b,g,k,q,j,x)p,v,b,g,k,q,j,x=u(p,v,b,g,k,q,j,x)p,v,b,g,k,q,j,x=u(p,v,b,g,k,q,j,x) -local z=s(p,b,k,j)local _=s(v,g,q,x) -local E={r(_,24)%256,r(_,16)%256,r(_,8)%256,_%256,r(z,24)%256,r(z,16)% -256,r(z,8)%256,z%256}return setmetatable(E,d)end;return{mac=c}end -a["ecnet.symmetric.sha256"]=function(...)local n=i("ecnet.util")local s=_G.bit;local h=2^32;local r=bit32 and -bit32.band or s.band -local d=bit32 and bit32.bnot or s.bnot;local l=bit32 and bit32.bxor or s.bxor;local u= -bit32 and bit32.lshift or s.blshift -local c=unpack or table.unpack;local m=n.byteTableMT -local function f(_,E)local T=_/ (2^E)local A=T%1;return(T-A)+A*h end;local function w(_,E)local T=_/ (2^E)return T-T%1 end -local y={0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19} -local p={0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2} -local function v(_)local E,T=0,0;if 0xFFFFFFFF-E<_ then T=T+1 -E=_- (0xFFFFFFFF-E)-1 else E=E+_ end;return T,E end -local function b(_,E)return -u((_[E]or 0),24)+u((_[E+1]or 0),16)+ -u((_[E+2]or 0),8)+ (_[E+3]or 0)end -local function g(_)local E=#_;local T={}_[#_+1]=0x80;while#_%64 ~=56 do _[#_+1]=0 end;local A=math.ceil(# -_/64) -for O=1,A do T[O]={}for I=1,16 do T[O][I]=b(_,1+ ((O-1)*64)+ -((I-1)*4))end end;T[A][15],T[A][16]=v(E*8)return T end -local function k(_,E) -for D=17,64 do -local L=l(l(f(_[D-15],7),f(_[D-15],18)),w(_[D-15],3)) -local U=l(l(f(_[D-2],17),f(_[D-2],19)),w(_[D-2],10))_[D]=(_[D-16]+L+_[D-7]+U)%h end;local T,A,O,I,N,S,H,R=c(E) -for D=1,64 do local L=l(l(f(N,6),f(N,11)),f(N,25)) -local U=l(r(N,S),r(d(N),H))local C=(R+L+U+p[D]+_[D])%h -local M=l(l(f(T,2),f(T,13)),f(T,22))local F=l(l(r(T,A),r(T,O)),r(A,O))local W=(M+F)%h;R,H,S,N,I,O,A,T=H,S,N,(I+ -C)%h,O,A,T,(C+W)%h end;E[1]=(E[1]+T)%h;E[2]=(E[2]+A)%h -E[3]=(E[3]+O)%h;E[4]=(E[4]+I)%h;E[5]=(E[5]+N)%h -E[6]=(E[6]+S)%h;E[7]=(E[7]+H)%h;E[8]=(E[8]+R)%h;return E end -local function q(_,E)local T={}for A=1,E do T[(A-1)*4+1]=r(w(_[A],24),0xFF) -T[(A-1)*4+2]=r(w(_[A],16),0xFF)T[(A-1)*4+3]=r(w(_[A],8),0xFF) -T[(A-1)*4+4]=r(_[A],0xFF)end;return -setmetatable(T,m)end -local function j(_)_=_ or""_=type(_)=="table"and{c(_)}or -{tostring(_):byte(1,-1)}_=g(_)local E={c(y)}for T=1,#_ do -E=k(_[T],E)end;return q(E,8)end -local function x(_,E)_=type(_)=="table"and{c(_)}or -{tostring(_):byte(1,-1)} -E=type(E)=="table"and -{c(E)}or{tostring(E):byte(1,-1)}local T=64;E=#E>T and j(E)or E;local A={}local O={}local I={}for N=1,T do -A[N]=l(0x36,E[N]or 0)O[N]=l(0x5C,E[N]or 0)end;for N=1,#_ do -A[T+N]=_[N]end;A=j(A)for N=1,T do I[N]=O[N]I[T+N]=A[N]end;return j(I)end -local function z(_,E,T,A) -E=type(E)=="table"and E or{tostring(E):byte(1,-1)}local O=32;A=A or 32;local I=1;local N={} -while A>0 do local S={}local H={c(E)}local R=A>O and O or A;H[# -H+1]=r(w(I,24),0xFF)H[#H+1]=r(w(I,16),0xFF) -H[#H+1]=r(w(I,8),0xFF)H[#H+1]=r(I,0xFF) -for D=1,T do H=x(H,_) -for L=1,R do S[L]=l(H[L],S[L]or 0)end;if D%200 ==0 then os.queueEvent("PBKDF2",D) -coroutine.yield("PBKDF2")end end;A=A-R;I=I+1;for D=1,R do N[#N+1]=S[D]end end;return setmetatable(N,m)end;return{digest=j,hmac=x,pbkdf2=z}end -a["ecnet.symmetric.random"]=function(...)local n=i("ecnet.symmetric.sha256") -local s=""local h=""local r="/.random"local function d(p)h=h.. (p or"")end;local function l() -s=tostring(n.digest(s..h))h=""end;if fs.exists(r)then local p=fs.open(r,"rb") -d(p.readAll())p.close()end;local u=os.epoch("utc") -local c=u;local m=1;d("init") -d(tostring(math.random(1,2^31-1)))d("|") -d(tostring(math.random(1,2^31-1)))d("|") -d(tostring(math.random(1,2^4)))d("|")d(tostring(u))d("|") -while(c-u<500)or(m<10000)do -c=os.epoch("utc")local p=tostring({}):sub(8)while#p<8 do p="0"..p end;d(string.char( -c%256)) -d(string.char(tonumber(p:sub(1,2),16))) -d(string.char(tonumber(p:sub(3,4),16))) -d(string.char(tonumber(p:sub(5,6),16))) -d(string.char(tonumber(p:sub(7,8),16)))m=m+1 end;l()d(tostring(os.epoch("utc")))l() -local function f() -d("save")d(tostring(os.epoch("utc"))) -d(tostring({}))l()local p=fs.open(r,"wb") -p.write(tostring(n.hmac("save",s)))s=tostring(n.digest(s))p.close()end;f() -local function w(p)d("seed") -d(tostring(os.epoch("utc")))d(tostring({}))d(p)l()f()end;local function y()d("random") -d(tostring(os.epoch("utc")))d(tostring({}))l()f()local p=n.hmac("out",s) -s=tostring(n.digest(s))return p end;return -{seed=w,save=f,random=y}end -a["ecnet.symmetric.chacha20"]=function(...)local n=i("ecnet.util")local s=bit32.bxor -local h=bit32.band;local r=bit32.lshift;local d=bit32.arshift;local l=_G.textutils;local u=n.byteTableMT -local c=2^32 -local m={("expand 16-byte k"):byte(1,-1)} -local f={("expand 32-byte k"):byte(1,-1)} -local function w(q,j)local x=q/ (2^ (32-j))local z=x%1;return(x-z)+z*c end -local function y(q,j,x,z,_)q[j]=(q[j]+q[x])%c;q[_]=w(s(q[_],q[j]),16)q[z]=( -q[z]+q[_])%c;q[x]=w(s(q[x],q[z]),12)q[j]=(q[j]+ -q[x])%c;q[_]=w(s(q[_],q[j]),8)q[z]= -(q[z]+q[_])%c;q[x]=w(s(q[x],q[z]),7)return q end -local function p(q,j)local x={table.unpack(q)} -for z=1,j do local _=z%2 ==1;x=_ and y(x,1,5,9,13)or -y(x,1,6,11,16) -x=_ and y(x,2,6,10,14)or y(x,2,7,12,13)x=_ and y(x,3,7,11,15)or y(x,3,8,9,14)x=_ and -y(x,4,8,12,16)or y(x,4,5,10,15)end;for z=1,16 do x[z]=(x[z]+q[z])%c end;return x end -local function v(q,j)return -(q[j+1]or 0)+r((q[j+2]or 0),8)+ -r((q[j+3]or 0),16)+r((q[j+4]or 0),24)end -local function b(q,j,x)local z=#q==32;local _=z and f or m;local E={}E[1]=v(_,0)E[2]=v(_,4) -E[3]=v(_,8)E[4]=v(_,12)E[5]=v(q,0)E[6]=v(q,4)E[7]=v(q,8)E[8]=v(q,12)E[9]=v(q, -z and 16 or 0)E[10]=v(q,z and 20 or 4)E[11]=v(q, -z and 24 or 8)E[12]=v(q,z and 28 or 12) -E[13]=x;E[14]=v(j,0)E[15]=v(j,4)E[16]=v(j,8)return E end -local function g(q)local j={}for x=1,16 do j[#j+1]=h(q[x],0xFF) -j[#j+1]=h(d(q[x],8),0xFF)j[#j+1]=h(d(q[x],16),0xFF) -j[#j+1]=h(d(q[x],24),0xFF)end;return j end -local function k(q,j,x,z,_) -assert(type(j)=="table","ChaCha20: Invalid key format ("..type(j).."), must be table") -assert(type(x)=="table","ChaCha20: Invalid nonce format ("..type(x).."), must be table") -assert(#j==16 or#j==32,"ChaCha20: Invalid key length ("..#j.."), must be 16 or 32") -assert(#x==12,"ChaCha20: Invalid nonce length ("..#x.."), must be 12") -q=type(q)=="table"and{table.unpack(q)}or{tostring(q):byte(1, --1)}z=tonumber(z)or 1;_=tonumber(_)or 20;local E={} -local T=b(j,x,z)local A=math.floor(#q/64) -for O=0,A do local I=g(p(T,_)) -T[13]=(T[13]+1)%c;local N={}for S=1,64 do N[S]=q[((O)*64)+S]end;for S=1,#N do -E[#E+1]=s(N[S],I[S])end;if O%1000 ==0 then os.queueEvent("") -os.pullEvent("")end end;return setmetatable(E,u)end;return{crypt=k}end -a["ecnet.symmetric.aecrypt"]=function(...)local n=i("ecnet.symmetric.chacha20") -local s=i("ecnet.symmetric.sha256")local h=i("ecnet.symmetric.siphash") -local r=i("ecnet.symmetric.random")local d=i("ecnet.util")local l=d.byteTableMT -local function u()local f={} -local w=os.epoch("utc")for y=1,7 do f[#f+1]=w%256;w=w/256;w=w-w%1 end;for y=8,12 do -f[y]=math.random(0,255)end;return f end -local function c(f,w,y)local p=u()local v=n.crypt(f,w,p,1,8)local b=p;for k=1,#v do b[#b+1]=v[k]end -local g=h.mac(b,{unpack(y,1,16)})for k=1,#g do b[#b+1]=g[k]end;return setmetatable(b,l)end -local function m(f,w,y)local f=type(f)=="table"and{unpack(f)}or -{tostring(f):byte(1,-1)} -local p=h.mac({unpack(f,1,#f-8)},{unpack(y,1,16)})local v={unpack(f,#f-7)}local b={unpack(f,13,#f-8)} -assert(p:isEqual(v),"invalid mac")local g={unpack(f,1,12)}local k=n.crypt(b,w,g,1,8)return -setmetatable(k,l)end;return{encrypt=c,decrypt=m}end -a["ecnet.ecnet"]=function(...)local n=i("ecnet.util")local s=i("ecnet.cbor") -local h=i("ecnet.symmetric.sha256")local r=i("ecnet.symmetric.chacha20") -local d=i("ecnet.symmetric.siphash")local l=i("ecnet.symmetric.aecrypt") -local u=i("ecnet.symmetric.random")local c=i("ecnet.ecc.ecc")local m=33635;local f="/.ecnet-secretseed" -local w=os.epoch("utc")local y={}local p=false -local function v(L)local U=h.digest(L)local C=U:toHex()local M="" -M=M..C:sub(1,4)M=M..":"M=M..C:sub(5,8)M=M..":"M=M..C:sub(9,12) -M=M..":"M=M..C:sub(13,16)M=M..":"M=M..C:sub(17,20)return M end;if not fs.exists(f)then local L=u.random() -L=string.char(unpack(L))n.saveFile(f,L)end -local b=n.loadFile(f)local g,k=c.keypair(b)local q=v(k) -local function j(L,U) -local C={type="addressRequest",from=q,to=U}L.transmit(m,m,C)local M={otherAddress=U}return M end -local function x(L,U)local C=v(U)local M -if y[C]then M=y[C].sharedSecret else M=c.exchange(g,U)end;local F,W=c.keypair()local Y=h.hmac("senderTagKey",M) -local P=os.epoch("utc")local V=h.hmac(tostring(W)..tostring(P),Y) -local B={type="connectionRequest",from=q,to=C,publicKey=tostring(k),ephemeralPublicKey=tostring(W),tag=tostring(V):sub(1,10),counter=P}L.transmit(m,m,B) -local G={ephemeralSecretKey=tostring(F),sharedSecret=M,otherAddress=C}return G end -local function z(L,U)local C=U.from -local M={type="addressResponse",from=q,to=C,publicKey=tostring(k)}L.transmit(m,m,M)end -local function _(L,U)local C=U.from;local M=U.publicKey;assert(L.otherAddress==C)assert( -type(M)=="string"and#M==22) -assert(v(M)==C)return M end -local function E(L,U)local C=U.publicKey;local M=U.ephemeralPublicKey;local F=U.from;local W=U.tag;local Y=U.counter;assert( -type(C)=="string"and#C==22)assert( -type(M)=="string")assert(#M==22) -assert(v(C)==F)assert(type(W)=="string"and#W==10)assert( -type(Y)=="number")if y[F]then -assert(Y>y[F].counter)else assert(Y>w)end;local P;if y[F]then P=y[F].sharedSecret else -P=c.exchange(g,C)end;local V,B=c.keypair()local G=c.exchange(V,M) -local K=h.hmac(G,P)local Q=h.hmac("senderEncryptionKey",K) -local J={unpack(h.hmac("senderMacKey",K),1,16)}local X=h.hmac("receiverEncryptionKey",K) -local Z={unpack(h.hmac("receiverMacKey",K),1,16)}local ee=h.hmac("senderTagKey",P) -local et=h.hmac("receiverTagKey",K) -assert( -tostring(h.hmac(M..tostring(Y),ee)):sub(1,10)==W)local Y=os.epoch("utc") -local ea=h.hmac(tostring(B)..tostring(Y),et) -local eo={type="connectionResponse",from=q,to=F,publicKey=tostring(k),ephemeralPublicKey=tostring(B),tag=tostring(ea):sub(1,10),counter=Y}L.transmit(m,m,eo) -y[F]={publicKey=C,sharedSecret=P,ownEncryptionKey=X,ownMacKey=Z,otherEncryptionKey=Q,otherMacKey=J,counter=Y}end -local function T(L,U)local C=U.publicKey;local M=U.ephemeralPublicKey;local F=U.from;local W=U.tag;local Y=U.counter;assert( -L.otherAddress==F) -assert(type(C)=="string"and#C==22)assert(type(M)=="string")assert(#M==22)assert(v(C)== -F) -assert(type(W)=="string"and#W==10)assert(type(Y)=="number")local P=L.ephemeralSecretKey -local V=L.sharedSecret;local B=c.exchange(P,M)local G=h.hmac(B,V) -local K=h.hmac("senderEncryptionKey",G) -local Q={unpack(h.hmac("senderMacKey",G),1,16)}local J=h.hmac("receiverEncryptionKey",G) -local X={unpack(h.hmac("receiverMacKey",G),1,16)}local Z=h.hmac("receiverTagKey",G) -assert( -tostring(h.hmac(M..tostring(Y),Z)):sub(1,10)==W) -y[F]={publicKey=C,sharedSecret=V,ownEncryptionKey=K,ownMacKey=Q,otherEncryptionKey=J,otherMacKey=X,counter=Y}end -local function A(L)local U=L.ciphertext;local C=L.from;local M=L.counter;local F=y[C].counter -assert(type(U)=="string")assert(type(C)=="string") -assert(type(M)=="number")assert(y[C])assert(M>F)local W=y[C].otherEncryptionKey -local Y=y[C].otherMacKey;local P=l.decrypt(U,W,Y)P=tostring(P)local V=0;for Q=1,6 do V=V*256 -V=V+P:byte(7-Q)end;local B=P:byte(7) -local G=#P-7- ((-B-1)%256)local K=P:sub(8,G+7)K=s.decode(K)assert(V>F)y[C].counter=V -return C,K end -local function O(L)local U;local C={L}local M=false;local F,W,Y -while true do -if not M then if#C>0 then W=table.remove(C,1) -U=coroutine.create(A)M=true end end -if M then F,W,Y=coroutine.resume(U,W)if -coroutine.status(U)=="dead"then if F then os.queueEvent("ecnet_message",W,Y)end -M=false end end;C[#C+1]=coroutine.yield()end end;local I=coroutine.wrap(O) -local function N(L) -while true do -while true do local U,C,M,C,F=os.pullEvent()if -U~="modem_message"then I()end;if M~=m then break end;if type(F)~="table"then break end;if F.to~= -q then break end -if F.type=="addressRequest"then pcall(z,L,F)elseif -F.type=="connectionRequest"then local W=pcall(E,L,F)if W then -os.queueEvent("ecnet_connection",F.from)end elseif F.type=="message"then I(F)end end end end -local function S(L,U,C) -local M=parallel.waitForAny(function()sleep(C)end,function()local F -if y[U]then F=y[U].publicKey else local Y=j(L,U) -while true do -local P,P,V,P,B=os.pullEvent("modem_message") -if -( -V==m and type(B)=="table"and B.to==q and B.from==U and B.type=="addressResponse")then local G;G,F=pcall(_,Y,B)if G then break end end end end;local W=x(L,F) -while true do local Y,Y,P,Y,V=os.pullEvent("modem_message") -if -( -P==m and type(V)== -"table"and V.to==q and V.from==U and V.type=="connectionResponse")then local B=pcall(T,W,V)if B then break end end end end,N)return(M==2)end -local function H(L,U,C)if not y[U]then return false end;local M=y[U].ownEncryptionKey -local F=y[U].ownMacKey;C=s.encode(C)local W=os.epoch("utc")local Y=W;local P="" -for G=1,6 do P=P.. -string.char(Y%256)Y=math.floor(Y/256)end;P=P..string.char(#C%256)P=P..C;P=P.. ("\0"):rep((-#C-1)% -256)local V=l.encrypt(P,M,F) -local B={type="message",from=q,to=U,ciphertext=tostring(V),counter=W}L.transmit(m,m,B)return true end -local function R(L,U,C)local M,F -local W=parallel.waitForAny(function()if C then sleep(C)else -while true do coroutine.yield()end end end,function() -while true do local Y -Y,M,F=os.pullEvent("ecnet_message")if not U or M==U then return end end end,function()N(L)end)if W==2 then return M,F else return nil end end -local function D(L)L.open(m) -return -{listen=function()return N(L)end,connect=function(U,C)return S(L,U,C)end,send=function(U,C) -return H(L,U,C)end,receive=function(U,C)return R(L,U,C)end}end;return{wrap=D,address=q}end -a["ecnet.ecc.modq"]=function(...)local n=i("ecnet.util") -local s=i("ecnet.ecc.arith")local h=i("ecnet.symmetric.sha256") -local r=i("ecnet.symmetric.random")local d=s.isEqual;local l=s.compare;local u=s.add;local c=s.sub;local m=s.addDouble;local f=s.mult -local w=s.square;local y=s.encodeInt;local p=s.decodeInt;local v=n.byteTableMT;local b -local g={9622359,6699217,13940450,16775734,16777215,16777215,3940351} -local k={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} -local q={15218585,5740955,3271338,9903997,9067368,7173545,6988392} -local j={1336213,11071705,9716828,11083885,9188643,1494868,3306114}local function x(C)local M={unpack(C)}if l(M,g)>=0 then M=c(M,g)end -return setmetatable(M,b)end -local function z(C,M)return x(u(C,M))end -local function _(C,M)local F=c(C,M)if F[7]<0 then F=u(F,g)end;return setmetatable(F,b)end -local function E(C) -local M={unpack(f({unpack(C,1,7)},q,true),1,7)}local F={unpack(m(C,f(M,g)),8,14)}return x(F)end;local function T(C,M)return E(f(C,M))end;local function A(C)return E(w(C))end;local function O(C) -return T(C,j)end -local function I(C)local C={unpack(C)}for M=8,14 do C[M]=0 end;return E(C)end;local N=O({1,0,0,0,0,0,0}) -local function S(C,M)local C={unpack(C)} -local F={unpack(N)}for W=1,168 do if M[W]==1 then F=T(F,C)end;C=A(C)end;return F end -local function H(C,M)local C={unpack(C)}local F=setmetatable({unpack(N)},b)if -M<0 then C=S(C,k)M=-M end;while M>0 do if M%2 ==1 then F=T(F,C)end;C=A(C)M=M/2 -M=M-M%1 end;return F end;local function R(C)local M=y(C)return setmetatable(M,v)end -local function D(C)C=type(C)== -"table"and{unpack(C,1,21)}or -{tostring(C):byte(1,21)}local M=p(C)M[7]= -M[7]%g[7]return setmetatable(M,b)end;local function L() -while true do local C={unpack(r.random(),1,21)}local M=p(C)if -M[7]0 then -K=H(K,B[O[Q]])elseif O[Q]<0 then K=D(K,B[-O[Q]])end end;return setmetatable(K,j)end;local W={I}for d=2,168 do W[d]=S(W[d-1])end -local function Y(d)local p=u(d,2) -local O={{unpack(x)},{unpack(z)},{unpack(z)}}for B=1,168 do -if p[B]==1 then O=H(O,W[B])elseif p[B]==-1 then O=D(O,W[B])end end;return setmetatable(O,j)end;local function P(d)d=L(d)local p={}local O,B=unpack(d)p=c(B)p[22]=O[1]%2 -return setmetatable(p,q)end -local function V(d) -d=type(d)=="table"and -{unpack(d,1,22)}or{tostring(d):byte(1,22)}local p=m(d)p[7]=p[7]%E[7]local O=w(p)local B=v(O,z)local G=v(f(_,O),z) -local K=w(B)local Q=f(B,K)local J=f(Q,K)local X=f(G,w(G))local Z=f(J,X) -local ee=f(Q,f(G,g(Z,A)))if ee[1]%2 ~=d[22]then ee=v(x,ee)end -local et={ee,p,{unpack(z)}}return setmetatable(et,j)end -j={__index={isOnCurve=function(d)return C(d)end,isInf=function(d)return d:isOnCurve()and M(d)end,encode=function(d)return -P(d)end},__tostring=function(d)return -d:encode():toHex()end,__add=function(d,p) -assert(d:isOnCurve(),"invalid point")assert(p:isOnCurve(),"invalid point")return H(d,p)end,__sub=function(d,p) -assert(d:isOnCurve(),"invalid point")assert(p:isOnCurve(),"invalid point")return D(d,p)end,__unm=function(d) -assert(d:isOnCurve(),"invalid point")return R(d)end,__eq=function(d,p) -assert(d:isOnCurve(),"invalid point")assert(p:isOnCurve(),"invalid point")return U(d,p)end,__mul=function(d,p)if -type(d)=="number"then return p*d end -if type(p)=="number"then -assert(p<2^24,"number multiplier too big")p={p,0,0,0,0,0,0}else p=k(p)end;if d==I then return Y(p)else return F(p,d)end end}I=setmetatable(I,j)N=setmetatable(N,j) -return{G=I,O=N,pointDecode=V}end -a["ecnet.ecc.arith"]=function(...) -local function n(p,v)return -( - -p[1]==v[1]and p[2]==v[2]and p[3]==v[3]and p[4]==v[4]and p[5]==v[5]and p[6]==v[6]and p[7]==v[7])end;local function s(p,v) -for b=7,1,-1 do if p[b]>v[b]then return 1 elseif p[b]0xffffff then g=g+1;b=b-0x1000000 end;if g>0xffffff then k=k+1;g=g-0x1000000 end;if k>0xffffff then q=q+1 -k=k-0x1000000 end;if q>0xffffff then j=j+1;q=q-0x1000000 end;if j> -0xffffff then x=x+1;j=j-0x1000000 end;if x>0xffffff then z=z+1 -x=x-0x1000000 end;return{b,g,k,q,j,x,z}end -local function r(p,v)local b=p[1]-v[1]local g=p[2]-v[2]local k=p[3]-v[3] -local q=p[4]-v[4]local j=p[5]-v[5]local x=p[6]-v[6]local z=p[7]-v[7]if b<0 then g=g-1 -b=b+0x1000000 end;if g<0 then k=k-1;g=g+0x1000000 end;if k<0 then q=q-1 -k=k+0x1000000 end;if q<0 then j=j-1;q=q+0x1000000 end;if j<0 then x=x-1 -j=j+0x1000000 end;if x<0 then z=z-1;x=x+0x1000000 end -return{b,g,k,q,j,x,z}end -local function d(p)local v=p[1]local b=p[2]local g=p[3]local k=p[4]local q=p[5]local j=p[6]local x=p[7]v=v/2 -v=v-v%1;v=v+ (b%2)*0x800000;b=b/2;b=b-b%1 -b=b+ (g%2)*0x800000;g=g/2;g=g-g%1;g=g+ (k%2)*0x800000;k=k/2;k=k-k%1;k=k+ -(q%2)*0x800000;q=q/2;q=q-q%1;q=q+ (j%2)*0x800000;j=j/2;j=j-j% -1;j=j+ (x%2)*0x800000;x=x/2;x=x-x%1 -return{v,b,g,k,q,j,x}end -local function l(p,v)local b=p[1]+v[1]local g=p[2]+v[2]local k=p[3]+v[3] -local q=p[4]+v[4]local j=p[5]+v[5]local x=p[6]+v[6]local z=p[7]+v[7] -local _=p[8]+v[8]local E=p[9]+v[9]local T=p[10]+v[10]local A=p[11]+v[11] -local O=p[12]+v[12]local I=p[13]+v[13]local N=p[14]+v[14]if b>0xffffff then g=g+1 -b=b-0x1000000 end;if g>0xffffff then k=k+1;g=g-0x1000000 end;if k> -0xffffff then q=q+1;k=k-0x1000000 end;if q>0xffffff then j=j+1 -q=q-0x1000000 end;if j>0xffffff then x=x+1;j=j-0x1000000 end;if x> -0xffffff then z=z+1;x=x-0x1000000 end;if z>0xffffff then _=_+1 -z=z-0x1000000 end;if _>0xffffff then E=E+1;_=_-0x1000000 end;if E> -0xffffff then T=T+1;E=E-0x1000000 end;if T>0xffffff then A=A+1 -T=T-0x1000000 end;if A>0xffffff then O=O+1;A=A-0x1000000 end;if O> -0xffffff then I=I+1;O=O-0x1000000 end;if I>0xffffff then N=N+1 -I=I-0x1000000 end;return{b,g,k,q,j,x,z,_,E,T,A,O,I,N}end -local function u(p,v,b)local g,k,q,j,x,z,_=unpack(p)local E,T,A,O,I,N,S=unpack(v)local H=g*E;local R=g*T+k*E;local D= -g*A+k*T+q*E;local L=g*O+k*A+q*T+j*E;local U=g*I+k*O+q*A+ -j*T+x*E;local C=g*N+k*I+q*O+j*A+x*T+ -z*E;local M= -g*S+k*N+q*I+j*O+x*A+z*T+_*E;local F,W,Y,P,V,B,G;if not b then F=k*S+q*N+j*I+x*O+ -z*A+_*T -W=q*S+j*N+x*I+z*O+_*A;Y=j*S+x*N+z*I+_*O;P=x*S+z*N+_*I;V=z*S+_*N -B=_*S;G=0 else F=0 end;local K;K=H;H= -H%0x1000000;R=R+ (K-H)/0x1000000;K=R;R=R%0x1000000;D= -D+ (K-R)/0x1000000;K=D;D=D%0x1000000 -L=L+ (K-D)/0x1000000;K=L;L=L%0x1000000;U=U+ (K-L)/0x1000000;K=U -U=U%0x1000000;C=C+ (K-U)/0x1000000;K=C;C=C%0x1000000 -M=M+ (K-C)/0x1000000;K=M;M=M%0x1000000 -if not b then F=F+ (K-M)/0x1000000;K=F -F=F%0x1000000;W=W+ (K-F)/0x1000000;K=W;W=W%0x1000000 -Y=Y+ (K-W)/0x1000000;K=Y;Y=Y%0x1000000;P=P+ (K-Y)/0x1000000;K=P -P=P%0x1000000;V=V+ (K-P)/0x1000000;K=V;V=V%0x1000000 -B=B+ (K-V)/0x1000000;K=B;B=B%0x1000000;G=G+ (K-B)/0x1000000 end;return{H,R,D,L,U,C,M,F,W,Y,P,V,B,G}end -local function c(p)local v,b,g,k,q,j,x=unpack(p)local z=v*v;local _=v*b*2;local E=v*g*2+b*b -local T=v*k*2+b*g*2;local A=v*q*2+b*k*2+g*g -local O=v*j*2+b*q*2+g*k*2;local I=v*x*2+b*j*2+g*q*2+k*k;local N=b*x*2+g*j*2+k* -q*2;local S=g*x*2+k*j*2+q*q;local H=k*x*2+ -q*j*2;local R=q*x*2+j*j;local D=j*x*2;local L=x*x;local U=0;local C;C=z -z=z%0x1000000;_=_+ (C-z)/0x1000000;C=_;_=_%0x1000000 -E=E+ (C-_)/0x1000000;C=E;E=E%0x1000000;T=T+ (C-E)/0x1000000;C=T -T=T%0x1000000;A=A+ (C-T)/0x1000000;C=A;A=A%0x1000000 -O=O+ (C-A)/0x1000000;C=O;O=O%0x1000000;I=I+ (C-O)/0x1000000;C=I -I=I%0x1000000;N=N+ (C-I)/0x1000000;C=N;N=N%0x1000000 -S=S+ (C-N)/0x1000000;C=S;S=S%0x1000000;H=H+ (C-S)/0x1000000;C=H -H=H%0x1000000;R=R+ (C-H)/0x1000000;C=R;R=R%0x1000000 -D=D+ (C-R)/0x1000000;C=D;D=D%0x1000000;L=L+ (C-D)/0x1000000;C=L -L=L%0x1000000;U=U+ (C-L)/0x1000000 -return{z,_,E,T,A,O,I,N,S,H,R,D,L,U}end -local function m(p)local v={}for b=1,7 do local g=p[b] -for k=1,3 do v[#v+1]=g%256;g=math.floor(g/256)end end;return v end -local function f(p)local v={}local b={} -for g=1,21 do local k=p[g] -assert(type(k)=="number","integer decoding failure") -assert(k>=0 and k<=255,"integer decoding failure")assert(k%1 ==0,"integer decoding failure")b[g]=k end -for g=1,21,3 do local k=0;for q=2,0,-1 do k=k*256;k=k+b[g+q]end;v[#v+1]=k end;return v end -local function w(p,v)local b=p[1]%2^v;if b>=2^ (v-1)then b=b-2^v end;return b end -local function y(p,v)local b={}local p={unpack(p)} -for g=1,168 do if p[1]%2 ==1 then b[#b+1]=w(p,v) -p=r(p,{b[#b],0,0,0,0,0,0})else b[#b+1]=0 end;p=d(p)end;return b end -return{isEqual=n,compare=s,add=h,sub=r,addDouble=l,mult=u,square=c,encodeInt=m,decodeInt=f,NAF=y}end -a["ecnet.cbor"]=function(...)local function n(ed,el)local eu,ec=pcall(i,ed)if not eu then return end -if el then return ec[el]end;return ec end -local s=function(ed) -local el,eu=pcall(loadstring or load,ed)if el and eu then return eu()end end;local h=setmetatable;local r=getmetatable -local d=debug and debug.getmetatable;local l=assert;local u=error;local c=type;local m=pairs;local f=ipairs;local w=tostring -local y=string.char;local p=table.concat;local v=table.sort;local b=math.floor;local g=math.abs -local k=math.huge;local q=math.max;local j=math.maxinteger or 9007199254740992;local x= -math.mininteger or-9007199254740992;local z=0/0 -local _=math.frexp -local E=math.ldexp or function(ed,el)return ed*2.0^el end -local T=math.type or function(ed)return -ed%1 ==0 and ed<=j and ed>=x and"integer"or"float"end;local A=string.pack or n("struct","pack")local O=string.unpack or -n("struct","unpack") -local I= -n("bit32","rshift")or n("bit","rshift")or s"return function(a,b) return a >> b end"or function(ed,el)return -q(0,b(ed/ (2^el)))end;if A and A(">I2",0)~="\0\0"then A=nil end;if O and -O(">I2","\1\2\3\4")~=0x102 then O=nil end;local N=nil;local S={}local function H(ed,el)return -S[c(ed)](ed,el)end -local function R(ed,el) -if el==0 and ed<0 then ed,el=-ed-1,32 end -if ed<24 then return y(el+ed)elseif ed<2^8 then return y(el+24,ed)elseif ed<2^16 then return -y(el+25,I(ed,8),ed%0x100)elseif ed<2^32 then return y(el+26,I(ed,24)%0x100,I(ed,16)%0x100,I(ed,8)%0x100, -ed%0x100)elseif ed< -2^64 then local eu=b(ed/2^32)ed=ed%2^32;return -y(el+27,I(eu,24)%0x100,I(eu,16)%0x100, -I(eu,8)%0x100,eu%0x100,I(ed,24)%0x100,I(ed,16)%0x100,I(ed,8)%0x100,ed%0x100)end;u"int too large"end -if A then -function R(ed,el)local eu;el=el or 0 -if ed<24 then eu,el=">B",el+ed elseif ed<256 then eu,el=">BB",el+24 elseif ed<65536 then -eu,el=">BI2",el+25 elseif ed<4294967296 then eu,el=">BI4",el+26 else eu,el=">BI8",el+27 end;return A(eu,el,ed)end end;local D={}function D:__tostring()return -self.name or("simple(%d)"):format(self.value)end -function D:__tocbor()return -self.cbor or R(self.value,224)end -local function L(ed,el,eu) -l(ed>=0 and ed<=255,"bad argument #1 to 'simple' (integer in range 0..255 expected)")return h({value=ed,name=el,cbor=eu},D)end;local U={}function U:__tostring() -return("%d(%s)"):format(self.tag,w(self.value))end;function U:__tocbor()return -R(self.tag,192)..H(self.value)end -local function C(ed,el) -l(ed>=0,"bad argument #1 to 'tagged' (positive integer expected)")return h({tag=ed,value=el},U)end;local M=L(22,"null")local F=L(23,"undefined") -local W=L(31,"break","\255")function S.number(ed)return S[T(ed)](ed)end;function S.integer(ed)if ed<0 then -return R(-1-ed,32)end;return R(ed,0)end -function S.float(ed)if -ed~=ed then return"\251\127\255\255\255\255\255\255\255"end;local el=( -ed>0 or 1/ed>0)and 0 or 1 -ed=g(ed)if ed==k then -return y(251,el*128+128-1).."\240\0\0\0\0\0\0"end;local eu,ec=_(ed)if eu==0 then return -y(251,el*128).."\0\0\0\0\0\0\0"end;eu=eu*2;ec=ec+1024-2;if -ec<=0 then eu=eu*2^ (ec-1)ec=0 else eu=eu-1 end -return -y(251,el*2^7+b(ec/ -2^4)%2^7,ec%2^4*2^4+b(eu*2^4%0x100),b( -eu*2^12%0x100),b(eu*2^20%0x100),b(eu*2^28%0x100),b( -eu*2^36%0x100),b(eu*2^44%0x100),b(eu*2^52%0x100))end -if A then function S.float(ed)return A(">Bd",251,ed)end end;function S.bytestring(ed)return R(#ed,64)..ed end;function S.utf8string(ed)return -R(#ed,96)..ed end;S.string=S.bytestring;function S.boolean(ed)return -ed and"\245"or"\244"end -S["nil"]=function()return"\246"end;function S.userdata(ed,el)local eu=d(ed)if eu then local ec=el and el[eu]or eu.__tocbor;if ec then return -ec(ed,el)end end -u"can't encode userdata"end -function S.table(ed,el) -local eu=r(ed)if eu then local ep=el and el[eu]or eu.__tocbor -if ep then return ep(ed,el)end end -local ec,em,ef,ew={R(#ed,128)},{"\191"},1,2;local ey=true -for ep,ev in m(ed)do ey=ey and ef==ep;ef=ef+1;local eb=H(ev,el)ec[ef]=eb;em[ew],ew=H(ep,el), -ew+1;em[ew],ew=eb,ew+1 end;em[1]=R(ef-1,160)return p(ey and ec or em)end;function S.array(ed,el)local eu={}for ec,em in f(ed)do eu[ec]=H(em,el)end -return R(#eu,128)..p(eu)end -function S.map(ed,el)local eu,ec,em={"\191"},2,0 -for ef,ew in -m(ed)do eu[ec],ec=H(ef,el),ec+1;eu[ec],ec=H(ew,el),ec+1;em=em+1 end;eu[1]=R(em,160)return p(eu)end;S.dict=S.map -function S.ordered_map(ed,el)local eu={}if not ed[1]then local ec=0 -for em in m(ed)do ec=ec+1;eu[ec]=em end;v(eu)end;for ec,em in f(ed[1]and ed or eu)do eu[ec]= -H(em,el)..H(ed[em],el)end;return -R(#eu,160)..p(eu)end -S["function"]=function()u"can't encode function"end;local function Y(ed,el)return ed:read(el)end;local function P(ed) -return ed:read(1):byte()end -local function V(ed,el) -if el<24 then return el elseif el<28 then local eu=0;for ec=1,2^ (el-24)do -eu=eu*256+P(ed)end;return eu else u"invalid length"end end;local B={}local function G(ed)local el=P(ed)return I(el,5),el%32 end;local function K(ed,el) -local eu,ec=G(ed)return B[eu](ed,ec,el)end -local function Q(ed,el)return V(ed,el)end;local function J(ed,el)return-1-V(ed,el)end -local function X(ed,el) -if el~=31 then return Y(ed,V(ed,el))end;local eu={}local ec=1;local em=K(ed) -while em~=W do eu[ec],ec=em,ec+1;em=K(ed)end;return p(eu)end;local function Z(ed,el)return X(ed,el)end -local function ee(ed,el,eu)local ec={} -if el==31 then local em=1;local ef=K(ed,eu)while ef~=W do ec[em],em=ef, -em+1;ef=K(ed,eu)end else local em=V(ed,el)for ef=1,em do -ec[ef]=K(ed,eu)end end;return ec end -local function et(ed,el,eu)local ec={}local em -if el==31 then local ef=1;em=K(ed,eu)while em~=W do ec[em],ef=K(ed,eu),ef+1 -em=K(ed,eu)end else local ef=V(ed,el) -for ew=1,ef do em=K(ed,eu)ec[em]=K(ed,eu)end end;return ec end;local ea={} -local function eo(ed,el,eu)local ec=V(ed,el)local em=K(ed,eu) -local ef=eu and eu[ec]or ea[ec]if ef then return ef(em)end;return C(ec,em)end -local function ei(ed)local el=P(ed)local eu=P(ed)local ec=el<128 and 1 or-1;eu=eu+ -(el*256)%1024;el=I(el,2)%32 -if el==0 then return ec*E(eu,-24)elseif el~=31 then return ec*E(eu+ -1024,el-25)elseif eu==0 then return ec*k else return z end end -local function en(ed)local el=P(ed)local eu=P(ed)local ec=el<128 and 1 or-1 -el=el*2%256+I(eu,7)eu=eu%128;eu=eu*256+P(ed)eu=eu*256+P(ed)if el==0 then return -ec*E(el,-149)elseif el~=0xff then return ec*E(eu+2^23,el-150)elseif eu==0 then -return ec*k else return z end end -local function es(ed)local el=P(ed)local eu=P(ed)local ec=el<128 and 1 or-1 -el=el%128*16+I(eu,4)eu=eu%16;eu=eu*256+P(ed)eu=eu*256+P(ed) -eu=eu*256+P(ed)eu=eu*256+P(ed)eu=eu*256+P(ed)eu=eu*256+P(ed) -if -el==0 then return ec*E(el,-149)elseif el~=0xff then -return ec*E(eu+2^52,el-1075)elseif eu==0 then return ec*k else return z end end;if O then function en(ed)return O(">f",Y(ed,4))end;function es(ed) -return O(">d",Y(ed,8))end end -local function eh(ed,el,eu)if el==24 then -el=P(ed)end -if el==20 then return false elseif el==21 then return true elseif el==22 then return M elseif el==23 then return F elseif el==25 then return ei(ed)elseif -el==26 then return en(ed)elseif el==27 then return es(ed)elseif el==31 then return W end;if eu and eu.simple then return eu.simple(el)end;return L(el)end;B[0]=Q;B[1]=J;B[2]=X;B[3]=Z;B[4]=ee;B[5]=et;B[6]=eo;B[7]=eh -local function er(ed,el)local eu={}local ec=1;local em -if -c(el)=="function"then em=el elseif c(el)=="table"then em=el.more elseif el~=nil then -u(("bad argument #2 to 'decode' (function or table expected, got %s)"):format(c(el)))end -if c(em)~="function"then function em()u"input too short"end end -function eu:read(ef)local ew=ed:sub(ec,ec+ef-1) -if#ew256 then ed=ed:sub(ec+1)ec=1 end;return#ef end;return K(eu,el)end -return{encode=H,decode=er,decode_file=K,type_encoders=S,type_decoders=B,tagged_decoders=ea,simple=L,tagged=C,null=M,undefined=F}end;return a["ecnet.ecnet"](...) \ No newline at end of file diff --git a/src/telem/vendor/ecnet2.lua b/src/telem/vendor/ecnet2.lua new file mode 100644 index 0000000..2e3427d --- /dev/null +++ b/src/telem/vendor/ecnet2.lua @@ -0,0 +1,33 @@ +local constants = require "ecnet2.constants" +local Identity = require "ecnet2.Identity" +local modems = require "ecnet2.modems" +local Protocol = require "ecnet2.Protocol" + +local module = {} + +--- @type ecnet2.Identity? +local identity + +local function fetchIdentity() + if not identity then identity = Identity(constants.IDENTITY_PATH) end + return identity +end + +--- Returns the address for this device. +--- @return string address The address. +function module.address() + return fetchIdentity().address +end + +module.open = modems.open +module.close = modems.close +module.isOpen = modems.isOpen + +--- Creates a protocol from a given interface. +--- @param interface ecnet2.IProtocol A table describing the protocol. +--- @return ecnet2.Protocol protocol The resulting protocol. +function module.Protocol(interface) + return Protocol(interface, fetchIdentity()) +end + +return module diff --git a/src/telem/vendor/ecnet2/CipherState.lua b/src/telem/vendor/ecnet2/CipherState.lua new file mode 100644 index 0000000..0bfccf8 --- /dev/null +++ b/src/telem/vendor/ecnet2/CipherState.lua @@ -0,0 +1,74 @@ +local class = require "ecnet2.class" +local aead = require "ccryptolib.aead" +local chacha = require "ccryptolib.chacha20" + +--- A symmetric encryption cipher state, containing a key and a numeric nonce. +--- @class ecnet2.CipherState +--- @field private k string? The current key. +--- @field private n number The current nonce. +local CipherState = class "ecnet2.CipherState" + +--- @param key string? A 32-byte key to initialize the state with. +function CipherState:initialise(key) + self.k = key + self.n = 0 +end + +--- Whether the state has a key or not. +--- @return boolean +function CipherState:hasKey() + return self.k ~= nil +end + +--- Sets the nonce to the given value. +--- @param nonce number +function CipherState:setNonce(nonce) + self.n = nonce +end + +--- Rekeys the cipher. +function CipherState:rekey() + self.k = chacha.crypt(self.k, (" s, se). +--- @param msk string The responder's masked secret key. +--- @param symmetricState ecnet2.SymmetricState The handshake's symmetric state. +--- @return ecnet2.HandshakeState +local function rC(msk, symmetricState) + local out = {} + + out.d = symmetricState:descriptor() + + function out.resolve(data) + if #data < 64 then return close() end + + local pk = symmetricState:decryptAndHash(data:sub(1, 48)) + if not pk then return close() end + + symmetricState:mixKey(x25519.exchange(x25519c.ephemeralSk(msk), pk)) + + local xm = unpad(symmetricState:decryptAndHash(data:sub(49))) + if not xm then return close() end + local ok, m = pcall(string.unpack, " s, se). +--- @param iPk string The initiator's static public key. +--- @param rPk string The responder's static public key. +--- @param se string The static-ephemeral shared secret for this handshake. +--- @param symmetricState ecnet2.SymmetricState The handshake's symmetric state. +--- @return ecnet2.HandshakeState +local function iC(iPk, rPk, se, symmetricState) + local out = {} + + out.maxlen = 2 ^ 15 - 1 + out.pk = rPk + + function out.send(msg) + local d = symmetricState:descriptor() + + local pkCtx = symmetricState:encryptAndHash(iPk) + + symmetricState:mixKey(se) + local xmsg = (" e, es). +--- @param msk string The responder's masked secret key. +--- @param rPk string The responder's static public key. +--- @param prologue string The handshake prologue. +--- @param introPsk string A pre-shared key resolved in the introduction. +--- @param data string The incoming network message, without the intro prefix. +--- @return ecnet2.HandshakeState +local function rA(msk, rPk, prologue, introPsk, data) + if #data < 64 then return close() end + + msk = x25519c.remask(msk) + + local symmetricState = SymmetricState() + symmetricState:mixHash(prologue) + symmetricState:mixHash(rPk) + symmetricState:mixKeyAndHash(introPsk) + + local eCtx = data:sub(1, 48) + local e = symmetricState:decryptAndHash(eCtx) + if not e then return close() end + + local es, ee = x25519c.exchange(msk, e) + symmetricState:mixKey(es) + + local m = unpad(symmetricState:decryptAndHash(data:sub(49))) + if not m then return close() end + + return rB(msk, ee, symmetricState) +end + +--- Returns a unique tag for a given (valid) connection request packet. +--- @param data string The incoming network message, without the intro prefix. +--- @return string tag A unique tag for this request. +local function getTag(data) + if #data < 64 then return "" end + return data:sub(33, 48) +end + +--- Initializes a handshake as the initiator, sending message A (-> e, es). +--- @param msk string The initiator's masked secret key. +--- @param iPk string The initiator's static public key. +--- @param rPk string The responder's static public key. +--- @param prologue string The handshake prologue. +--- @param introPsk string A pre-shared key resolved in the introduction. +--- @return ecnet2.HandshakeState +--- @return string data The raw data to send over the network. +local function iA(msk, iPk, rPk, prologue, introPsk) + msk = x25519c.remask(msk) + + local symmetricState = SymmetricState() + symmetricState:mixHash(prologue) + symmetricState:mixHash(rPk) + symmetricState:mixKeyAndHash(introPsk) + + local esk = x25519c.ephemeralSk(msk) + local eCtx = symmetricState:encryptAndHash(x25519.publicKey(esk)) + + symmetricState:mixKey(x25519.exchange(esk, rPk)) + local ctx = symmetricState:encryptAndHash(pad("", 32 + 48 + 16, 192)) + + return iB(msk, iPk, rPk, symmetricState), eCtx .. ctx +end + +return { + iA = iA, + rA = rA, + getTag = getTag, + close = close, +} diff --git a/src/telem/vendor/ecnet2/Identity.lua b/src/telem/vendor/ecnet2/Identity.lua new file mode 100644 index 0000000..342cc3d --- /dev/null +++ b/src/telem/vendor/ecnet2/Identity.lua @@ -0,0 +1,85 @@ +local class = require "ecnet2.class" +local random = require "ccryptolib.random" +local blake3 = require "ccryptolib.blake3" +local x25519 = require "ccryptolib.x25519" +local x25519c = require "ccryptolib.x25519c" +local addressEncoder = require "ecnet2.addressEncoder" +local Protocol = require "ecnet2.Protocol" + +local ID_PATH = "id.bin" +local ID_DEL_PATH = "id.bin.del" +local ID_BACKUP_PATH = "id.bin.bak" +local ADDRESS_PATH = "address.txt" + +local NOISE_SIZE = 512 + +--- @return string +local function mkNoise() + local body = random.random(NOISE_SIZE - 32) + local checksum = blake3.digest(body) + return checksum .. body +end + +--- @param noise string? +--- @return string? +local function mkKeyFromNoise(noise) + if not noise then return end + local checksum = blake3.digest(noise:sub(33)) + if noise:sub(1, 32) ~= checksum then return end + return blake3.digest(noise) +end + +--- @class ecnet2.Identity Identifies a peer to other connected devices. +--- @field _msk string The masked secret key for the identity. +--- @field _pk string The public key for the identity. +--- @field address string The address for connecting to this device +local Identity = class "ecnet2.Identity" + +--- @param path string +function Identity:initialise(path) + local idPath = fs.combine(path, ID_PATH) + local idDelPath = fs.combine(path, ID_DEL_PATH) + local idBackupPath = fs.combine(path, ID_BACKUP_PATH) + local addressPath = fs.combine(path, ADDRESS_PATH) + + --#region critical section on the directory + fs.makeDir(path) + if fs.exists(idDelPath) then + fs.delete(path) + fs.makeDir(path) + end + + local noise + if fs.exists(idPath) then + local f = assert(fs.open(idPath, "rb")) + noise = f.readAll() + f.close() + else + noise = mkNoise() + local f = assert(fs.open(idDelPath, "wb")) + f.write(noise) + f.close() + fs.copy(idDelPath, idBackupPath) + fs.move(idDelPath, idPath) + end + + local sk = assert(mkKeyFromNoise(noise), "identity file is corrupted") + local pk = x25519.publicKey(sk) + local addr = addressEncoder.encode(pk) + local f = assert(fs.open(addressPath, "wb")) + f.write(addr) + f.close() + --#endregion + + self._msk = x25519c.mask(sk) + self._pk = pk + self.address = addr +end + +--- Creates a protocol from a given interface on this identity. +--- @return ecnet2.Protocol +function Identity:Protocol(interface) + return Protocol(interface, self) +end + +return Identity diff --git a/src/telem/vendor/ecnet2/Listener.lua b/src/telem/vendor/ecnet2/Listener.lua new file mode 100644 index 0000000..1ad1723 --- /dev/null +++ b/src/telem/vendor/ecnet2/Listener.lua @@ -0,0 +1,79 @@ +local class = require "ecnet2.class" +local uid = require "ecnet2.uid" +local blake3 = require "ccryptolib.blake3" +local ecnetd = require "ecnet2.ecnetd" +local modems = require "ecnet2.modems" +local HandshakeState = require "ecnet2.HandshakeState" +local Connection = require "ecnet2.Connection" + +--- A listener for incoming connection requests. +--- @class ecnet2.Listener +--- @field _protocol ecnet2.Protocol The listener's protocol. +--- @field _psk string The PSK for incoming connections in this listener. +--- @field _handler function The packet handler function. +--- @field _processed table Processed connections. +--- @field id string The connection's ID, used in `ecnet2_message` events. +local Listener = class "ecnet2.Listener" + +--- @param protocol ecnet2.Protocol +function Listener:initialise(protocol) + self.id = uid() + self._psk = blake3.digest(protocol._identity._pk .. protocol._hash) + self._protocol = protocol + self._processed = setmetatable({}, { __mode = "v" }) + self._handler = function(m, s) return self:_handle(m, s) end + local descriptor = blake3.digest(self._psk) + ecnetd.handlers[descriptor] = self._handler +end + +--- Handles an incoming packet. +--- @param packet string +--- @param side string +function Listener:_handle(packet, side) + local request = { _lid = self.id, _nid = side, _packet = packet } + os.queueEvent("ecnet2_request", self.id, request, side) +end + +--- Accepts a request and builds a connection. Waits for the next request if +--- none are provided. +--- +--- Throws `"invalid listener for this request"` if the supplied request isn't +--- meant for this listener. +--- +--- Returns a dummy connection if the request is malformed, or if the request +--- has already been accepted. +--- +--- @param reply any +--- @param request table? +--- @return ecnet2.Connection +function Listener:accept(reply, request) + -- Wait for the request if not given. + while not request do + local _, id, req = os.pullEvent("ecnet2_request") + if id == self.id then request = req end + end + + -- If the tag has already been processed, return a dummy connection. + local tag = HandshakeState.getTag(request._packet) + if self._processed[tag] then + return Connection(HandshakeState.close(), self._protocol, request._nid) + end + + assert(request._lid == self.id, "invalid listener for this request") + + local msk = self._protocol._identity._msk + local pk = self._protocol._identity._pk + local state = HandshakeState.rA(msk, pk, "", self._psk, request._packet) + + local str = self._protocol._interface.serialize(reply) + assert(type(str) == "string", "serializer returned non-string") + assert(#str <= state.maxlen, "serialized message is too large") + local newState, packet = state.send(str) + if packet then modems.transmit(request._nid, packet) end + + local connection = Connection(newState, self._protocol, request._nid) + self._processed[tag] = connection + return connection +end + +return Listener diff --git a/src/telem/vendor/ecnet2/Protocol.lua b/src/telem/vendor/ecnet2/Protocol.lua new file mode 100644 index 0000000..b883b06 --- /dev/null +++ b/src/telem/vendor/ecnet2/Protocol.lua @@ -0,0 +1,58 @@ +local class = require "ecnet2.class" +local expect = require "cc.expect" +local HandshakeState = require "ecnet2.HandshakeState" +local addressEncoder = require "ecnet2.addressEncoder" +local blake3 = require "ccryptolib.blake3" +local Connection = require "ecnet2.Connection" +local Listener = require "ecnet2.Listener" +local modems = require "ecnet2.modems" + +--- A namespace for interpreting messages received over connections. +--- @class ecnet2.Protocol +--- @field _interface ecnet2.IProtocol The underlying interface. +--- @field _identity ecnet2.Identity The protocol's identity. +--- @field _hash string The protocol name hash. +local Protocol = class "ecnet2.Protocol" + +--- The interface for describing a new protocol. +--- @class ecnet2.IProtocol +--- @field name string The protocol's name. +--- @field serialize fun(obj: any): string The serializer for messages. +--- @field deserialize fun(str: string): any The deserializer for messages. + +--- @param interface ecnet2.IProtocol +--- @param identity ecnet2.Identity The protocol's identity. +function Protocol:initialise(interface, identity) + expect.field(interface, "name", "string") + expect.field(interface, "serialize", "function") + expect.field(interface, "deserialize", "function") + self._interface = interface + self._identity = identity + self._hash = blake3.digest(interface.name) +end + +--- Creates a new connection using this protocol and a modem side. +--- @param address string The responder's address. +--- @param modem string The modem name to connect through. +--- @return ecnet2.Connection +function Protocol:connect(address, modem) + expect.expect(1, address, "string") + expect.expect(2, modem, "string") + assert(modems.isOpen(modem), "modem isn't open: " .. modem) + local rpk = assert(addressEncoder.parse(address), "invalid address") + local psk = blake3.digest(rpk .. self._hash) + local descriptor = blake3.digest(psk) + local msk = self._identity._msk + local lpk = self._identity._pk + local state, data = HandshakeState.iA(msk, lpk, rpk, "", psk) + modems.transmit(modem, descriptor .. data) + return Connection(state, self, modem) +end + +--- Creates a listener for this protocol on all open modems. +--- @return ecnet2.Listener +function Protocol:listen() + return Listener(self) +end + +return Protocol diff --git a/src/telem/vendor/ecnet2/SymmetricState.lua b/src/telem/vendor/ecnet2/SymmetricState.lua new file mode 100644 index 0000000..8bb109a --- /dev/null +++ b/src/telem/vendor/ecnet2/SymmetricState.lua @@ -0,0 +1,86 @@ +local class = require "ecnet2.class" +local blake3 = require "ccryptolib.blake3" +local CipherState = require "ecnet2.CipherState" + +--- A symmetric state containing keys and a handshake transcript hash. +--- @class ecnet2.SymmetricState +--- @field private h string The current handshake transcript hash. +--- @field private ck string The current chaining key for deriving other keys. +--- @field private cs ecnet2.CipherState The current encryption CipherState. +local SymmetricState = class "ecnet2.SymmetricState" + +-- We modify Noise so much that it's meaningless to use their naming standard. +local PROTOCOL_NAME = blake3.digest + "ecnet2 2023-01-03 04:16 UTC network handshake protocol" + +local DESCRIPTOR_KDF = blake3.deriveKey + "ecnet2 2023-01-05 00:00 UTC handshake descriptor context" + +function SymmetricState:initialise() + self.h = PROTOCOL_NAME + self.ck = PROTOCOL_NAME + self.cs = CipherState(nil) +end + +--- Mixes keying material into the key and the transcript hash. +--- @param material string +function SymmetricState:mixKeyAndHash(material) + local tk = blake3.digestKeyed(self.ck, material, 96) + self.ck = tk:sub(1, 32) + self:mixHash(tk:sub(33, 64)) + self.cs = CipherState(tk:sub(65)) +end + +--- Mixes keying material into the key. +--- @param material string +function SymmetricState:mixKey(material) + local tk = blake3.digestKeyed(self.ck, material, 64) + self.ck = tk:sub(1, 32) + self.cs = CipherState(tk:sub(33)) +end + +--- Mixes data into the transcript hash. +--- @param data string +--- @return string data The input data. +function SymmetricState:mixHash(data) + self.h = blake3.digest(self.h .. data) + return data +end + +--- Returns the current transcript hash. +--- @return string hash The handshake transcript hash. +function SymmetricState:getHandshakeHash() + return self.h +end + +--- Encrypts data and adds it to the transcript. +--- @param plaintext string The plaintext to encrypt. +--- @return string ciphertext The encrypted plaintext +function SymmetricState:encryptAndHash(plaintext) + local ciphertext = self.cs:encryptWithAd(self.h, plaintext) + return self:mixHash(ciphertext) +end + +--- Adds data to the transcript and tries to decrypt it. +--- @param ciphertext string The ciphertext to decrypt. +--- @return string? plaintext The decrypted plaintext, or nil on failure. +function SymmetricState:decryptAndHash(ciphertext) + local plaintext = self.cs:decryptWithAd(self.h, ciphertext) + self:mixHash(ciphertext) + return plaintext +end + +--- Returns the current descriptor for the state. +--- @return string descriptor The descriptor. +function SymmetricState:descriptor() + return DESCRIPTOR_KDF(self.ck .. self.h) +end + +--- Splits the state into two cipher states, finishing the handshake. +--- @return ecnet2.CipherState, ecnet2.CipherState +function SymmetricState:split() + local tk = blake3.digestKeyed(self.ck, "", 64) + return CipherState(tk:sub(1, 32)), CipherState(tk:sub(33)) +end + +return SymmetricState diff --git a/src/telem/vendor/ecnet2/addressEncoder.lua b/src/telem/vendor/ecnet2/addressEncoder.lua new file mode 100644 index 0000000..d00e50b --- /dev/null +++ b/src/telem/vendor/ecnet2/addressEncoder.lua @@ -0,0 +1,54 @@ +local expect = require "cc.expect" + +local band = bit32.band + +local alphabet = {} +local ralphabet = {} +do + local s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" + for i, ch in (s):gmatch("()(.)") do + alphabet[i - 1] = ch + ralphabet[ch] = i - 1 + end +end + +--- Encodes a public key to an address. +--- @param publicKey string The 32-byte public key. +--- @return string address The encoded address. +local function encode(publicKey) + expect(1, publicKey, "string") + assert(#publicKey == 32, "invalid public key") + publicKey = publicKey .. "\0" + local out = "" + for block in (publicKey):gmatch("...") do + local val = (">I3"):unpack(block) + local mul = 2 ^ -18 + for _ = 0, 18, 6 do + out = out .. alphabet[band(val * mul, 63)] + mul = mul * 64 + end + end + return out:sub(1, 43) .. "=" +end + +--- Decodes an address to a public key. +--- @param address string The address to decode. +--- @return string? publicKey The decoded public key, or nil on failure. +local function parse(address) + if type(address) ~= "string" then return end + if #address ~= 44 then return end + if not address:match("^[A-Za-z0-9%-_]*=$") then return end + address = address:sub(1, 43) .. "A" + local bytes = "" + for block in address:gmatch("....") do + local val = 0 + for i = 1, 4 do val = val * 64 + ralphabet[block:sub(i, i)] end + bytes = bytes .. (">I3"):pack(val) + end + return bytes:sub(1, 32) +end + +return { + encode = encode, + parse = parse, +} diff --git a/src/telem/vendor/ecnet2/class.lua b/src/telem/vendor/ecnet2/class.lua new file mode 100644 index 0000000..195e368 --- /dev/null +++ b/src/telem/vendor/ecnet2/class.lua @@ -0,0 +1,60 @@ +--[[ +Copyright 2018-2023 SquidDev + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +--- A tiny "class" implementation. +-- +-- This does not support inheritance, operators, or anything complex - it's +-- just a way of enabling method calls. + +local expect = require "cc.expect".expect + +local function tostring_instance(self) return self.__name .. "<>" end +local function tostring_class(self) return "Class<" .. self.__name .. ">" end + +local class_mt = { + __tostring = tostring_class, + __name = "class", + __call = function(self, ...) + local tbl = setmetatable({}, self.__index) + tbl:initialise(...) + return tbl + end, +} + +return function(name) + expect(1, name, "string") + + local class = setmetatable({ + __name = name, + __tostring = tostring_instance, + }, class_mt) + + class.__index = class + return class +end diff --git a/src/telem/vendor/ecnet2/constants.lua b/src/telem/vendor/ecnet2/constants.lua new file mode 100644 index 0000000..2c7e777 --- /dev/null +++ b/src/telem/vendor/ecnet2/constants.lua @@ -0,0 +1,4 @@ +return { + CHANNEL = 33635, + IDENTITY_PATH = "/.ecnet2", +} diff --git a/src/telem/vendor/ecnet2/ecnetd.lua b/src/telem/vendor/ecnet2/ecnetd.lua new file mode 100644 index 0000000..cca61bf --- /dev/null +++ b/src/telem/vendor/ecnet2/ecnetd.lua @@ -0,0 +1,40 @@ +local redrun = require "redrun" +local constants = require "ecnet2.constants" + +--- The global daemon state. +--- @class ecnet2.EcnetdState +--- @field handlers table +local state + +--- @param message string +local function enqueue(message, side) + if type(message) ~= "string" then return end + if #message >= 2 ^ 16 then return end + if #message < 32 then return end + local descriptor = message:sub(1, 32) + local etc = message:sub(33) + local handler = state.handlers[descriptor] + if handler then return handler(etc, side) end +end + +local function ecnetd() + while not state do coroutine.yield() end + while true do + local _, side, ch, _, msg = coroutine.yield("modem_message") + if ch == constants.CHANNEL then enqueue(msg, side) end + end +end + +-- Spin up a daemon to handle incoming modem messages if none exist. +local id = redrun.getid("ecnetd") +if id then + state = assert(redrun.getstate(id)) +else + id = redrun.start(ecnetd, "ecnetd") + state = assert(redrun.getstate(id)) + state.handlers = setmetatable({}, { __mode = "v" }) +end + +return { + handlers = state.handlers, +} diff --git a/src/telem/vendor/ecnet2/modems.lua b/src/telem/vendor/ecnet2/modems.lua new file mode 100644 index 0000000..8b3c0ca --- /dev/null +++ b/src/telem/vendor/ecnet2/modems.lua @@ -0,0 +1,57 @@ +local expect = require "cc.expect" +local constants = require "ecnet2.constants" + +--- Opens a modem with the given peripheral name for exchanging messages. +--- @param modem string +local function open(modem) + expect.expect(1, modem, "string") + assert(peripheral.getType(modem) == "modem", "no such modem: " .. modem) + peripheral.call(modem, "open", constants.CHANNEL) +end + +--- Closes a modem with the given peripheral name, or all modems if not given. +--- @param modem string? +local function close(modem) + expect.expect(1, modem, "string", "nil") + if modem then + assert(peripheral.getType(modem) == "modem", "no such modem: " .. modem) + return peripheral.call(modem, "close", constants.CHANNEL) + else + peripheral.find("modem", close) + end +end + +--- Returns whether a modem is currently open, or any modem if not given. +--- @param modem string? +--- @return boolean +local function isOpen(modem) + expect.expect(1, modem, "string", "nil") + if modem then + if peripheral.getType(modem) ~= "modem" then return false end + return peripheral.call(modem, "isOpen", constants.CHANNEL) + else + return not not peripheral.find("modem", isOpen) + end +end + +--- Transmits a packet on all open modems. +--- @param side string +--- @param packet string +--- @return boolean +local function transmit(side, packet) + return pcall( + peripheral.call, + side, + "transmit", + constants.CHANNEL, + constants.CHANNEL, + packet + ) +end + +return { + open = open, + close = close, + isOpen = isOpen, + transmit = transmit, +} diff --git a/src/telem/vendor/ecnet2/uid.lua b/src/telem/vendor/ecnet2/uid.lua new file mode 100644 index 0000000..2d9f294 --- /dev/null +++ b/src/telem/vendor/ecnet2/uid.lua @@ -0,0 +1,19 @@ +--- Unique string ID generator. +-- It's just a random string concatenated to a counter. IDs are unique but not +-- uniformly random. Multiple instances of the generator are safe to use and +-- will be unique in relation to each other. + +local random = require "ccryptolib.random" + +local counter, suffix + +--- Returns a unique ID +--- @return string uid A 32-byte unique string id. +return function() + if not suffix or counter >= 2 ^ 32 then + suffix = random.random(28) + counter = 0 + end + counter = counter + 1 + return ("