diff --git a/.gitignore b/.gitignore index aa34b5aaf7..704714139f 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ src/Data/TimelessJewelData/*.bin # Simplegraphic Debugging runtime/imgui.ini runtime/SimpleGraphic/SimpleGraphic.log + +src/poe_api_response.json \ No newline at end of file diff --git a/runtime/lua/sha2.lua b/runtime/lua/sha2.lua new file mode 100644 index 0000000000..201f52ea67 --- /dev/null +++ b/runtime/lua/sha2.lua @@ -0,0 +1,5675 @@ +-------------------------------------------------------------------------------------------------------------------------- +-- sha2.lua +-------------------------------------------------------------------------------------------------------------------------- +-- VERSION: 12 (2022-02-23) +-- AUTHOR: Egor Skriptunoff +-- LICENSE: MIT (the same license as Lua itself) +-- URL: https://github.com/Egor-Skriptunoff/pure_lua_SHA +-- +-- DESCRIPTION: +-- This module contains functions to calculate SHA digest: +-- MD5, SHA-1, +-- SHA-224, SHA-256, SHA-512/224, SHA-512/256, SHA-384, SHA-512, +-- SHA3-224, SHA3-256, SHA3-384, SHA3-512, SHAKE128, SHAKE256, +-- HMAC, +-- BLAKE2b, BLAKE2s, BLAKE2bp, BLAKE2sp, BLAKE2Xb, BLAKE2Xs, +-- BLAKE3, BLAKE3_KDF +-- Written in pure Lua. +-- Compatible with: +-- Lua 5.1, Lua 5.2, Lua 5.3, Lua 5.4, Fengari, LuaJIT 2.0/2.1 (any CPU endianness). +-- Main feature of this module: it was heavily optimized for speed. +-- For every Lua version the module contains particular implementation branch to get benefits from version-specific features. +-- - branch for Lua 5.1 (emulating bitwise operators using look-up table) +-- - branch for Lua 5.2 (using bit32/bit library), suitable for both Lua 5.2 with native "bit32" and Lua 5.1 with external library "bit" +-- - branch for Lua 5.3/5.4 (using native 64-bit bitwise operators) +-- - branch for Lua 5.3/5.4 (using native 32-bit bitwise operators) for Lua built with LUA_INT_TYPE=LUA_INT_INT +-- - branch for LuaJIT without FFI library (useful in a sandboxed environment) +-- - branch for LuaJIT x86 without FFI library (LuaJIT x86 has oddity because of lack of CPU registers) +-- - branch for LuaJIT 2.0 with FFI library (bit.* functions work only with Lua numbers) +-- - branch for LuaJIT 2.1 with FFI library (bit.* functions can work with "int64_t" arguments) +-- +-- +-- USAGE: +-- Input data should be provided as a binary string: either as a whole string or as a sequence of substrings (chunk-by-chunk loading, total length < 9*10^15 bytes). +-- Result (SHA digest) is returned in hexadecimal representation as a string of lowercase hex digits. +-- Simplest usage example: +-- local sha = require("sha2") +-- local your_hash = sha.sha256("your string") +-- See file "sha2_test.lua" for more examples. +-- +-- +-- CHANGELOG: +-- version date description +-- ------- ---------- ----------- +-- 12 2022-02-23 Now works in Luau (but NOT optimized for speed) +-- 11 2022-01-09 BLAKE3 added +-- 10 2022-01-02 BLAKE2 functions added +-- 9 2020-05-10 Now works in OpenWrt's Lua (dialect of Lua 5.1 with "double" + "invisible int32") +-- 8 2019-09-03 SHA-3 functions added +-- 7 2019-03-17 Added functions to convert to/from base64 +-- 6 2018-11-12 HMAC added +-- 5 2018-11-10 SHA-1 added +-- 4 2018-11-03 MD5 added +-- 3 2018-11-02 Bug fixed: incorrect hashing of long (2 GByte) data streams on Lua 5.3/5.4 built with "int32" integers +-- 2 2018-10-07 Decreased module loading time in Lua 5.1 implementation branch (thanks to Peter Melnichenko for giving a hint) +-- 1 2018-10-06 First release (only SHA-2 functions) +----------------------------------------------------------------------------- + + +local print_debug_messages = false -- set to true to view some messages about your system's abilities and implementation branch chosen for your system + +local unpack, table_concat, byte, char, string_rep, sub, gsub, gmatch, string_format, floor, ceil, math_min, math_max, tonumber, type, math_huge = + table.unpack or unpack, table.concat, string.byte, string.char, string.rep, string.sub, string.gsub, string.gmatch, string.format, math.floor, math.ceil, math.min, math.max, tonumber, type, math.huge + + +-------------------------------------------------------------------------------- +-- EXAMINING YOUR SYSTEM +-------------------------------------------------------------------------------- + +local function get_precision(one) + -- "one" must be either float 1.0 or integer 1 + -- returns bits_precision, is_integer + -- This function works correctly with all floating point datatypes (including non-IEEE-754) + local k, n, m, prev_n = 0, one, one + while true do + k, prev_n, n, m = k + 1, n, n + n + 1, m + m + k % 2 + if k > 256 or n - (n - 1) ~= 1 or m - (m - 1) ~= 1 or n == m then + return k, false -- floating point datatype + elseif n == prev_n then + return k, true -- integer datatype + end + end +end + +-- Make sure Lua has "double" numbers +local x = 2/3 +local Lua_has_double = x * 5 > 3 and x * 4 < 3 and get_precision(1.0) >= 53 +assert(Lua_has_double, "at least 53-bit floating point numbers are required") + +-- Q: +-- SHA2 was designed for FPU-less machines. +-- So, why floating point numbers are needed for this module? +-- A: +-- 53-bit "double" numbers are useful to calculate "magic numbers" used in SHA. +-- I prefer to write 50 LOC "magic numbers calculator" instead of storing more than 200 constants explicitly in this source file. + +local int_prec, Lua_has_integers = get_precision(1) +local Lua_has_int64 = Lua_has_integers and int_prec == 64 +local Lua_has_int32 = Lua_has_integers and int_prec == 32 +assert(Lua_has_int64 or Lua_has_int32 or not Lua_has_integers, "Lua integers must be either 32-bit or 64-bit") + +-- Q: +-- Does it mean that almost all non-standard configurations are not supported? +-- A: +-- Yes. Sorry, too many problems to support all possible Lua numbers configurations. +-- Lua 5.1/5.2 with "int32" will not work. +-- Lua 5.1/5.2 with "int64" will not work. +-- Lua 5.1/5.2 with "int128" will not work. +-- Lua 5.1/5.2 with "float" will not work. +-- Lua 5.1/5.2 with "double" is OK. (default config for Lua 5.1, Lua 5.2, LuaJIT) +-- Lua 5.3/5.4 with "int32" + "float" will not work. +-- Lua 5.3/5.4 with "int64" + "float" will not work. +-- Lua 5.3/5.4 with "int128" + "float" will not work. +-- Lua 5.3/5.4 with "int32" + "double" is OK. (config used by Fengari) +-- Lua 5.3/5.4 with "int64" + "double" is OK. (default config for Lua 5.3, Lua 5.4) +-- Lua 5.3/5.4 with "int128" + "double" will not work. +-- Using floating point numbers better than "double" instead of "double" is OK (non-IEEE-754 floating point implementation are allowed). +-- Using "int128" instead of "int64" is not OK: "int128" would require different branch of implementation for optimized SHA512. + +-- Check for LuaJIT and 32-bit bitwise libraries +local is_LuaJIT = ({false, [1] = true})[1] and _VERSION ~= "Luau" and (type(jit) ~= "table" or jit.version_num >= 20000) -- LuaJIT 1.x.x and Luau are treated as vanilla Lua 5.1/5.2 +local is_LuaJIT_21 -- LuaJIT 2.1+ +local LuaJIT_arch +local ffi -- LuaJIT FFI library (as a table) +local b -- 32-bit bitwise library (as a table) +local library_name + +if is_LuaJIT then + -- Assuming "bit" library is always available on LuaJIT + b = require"bit" + library_name = "bit" + -- "ffi" is intentionally disabled on some systems for safety reason + local LuaJIT_has_FFI, result = pcall(require, "ffi") + if LuaJIT_has_FFI then + ffi = result + end + is_LuaJIT_21 = not not loadstring"b=0b0" + LuaJIT_arch = type(jit) == "table" and jit.arch or ffi and ffi.arch or nil +else + -- For vanilla Lua, "bit"/"bit32" libraries are searched in global namespace only. No attempt is made to load a library if it's not loaded yet. + for _, libname in ipairs(_VERSION == "Lua 5.2" and {"bit32", "bit"} or {"bit", "bit32"}) do + if type(_G[libname]) == "table" and _G[libname].bxor then + b = _G[libname] + library_name = libname + break + end + end +end + +-------------------------------------------------------------------------------- +-- You can disable here some of your system's abilities (for testing purposes) +-------------------------------------------------------------------------------- +-- is_LuaJIT = nil +-- is_LuaJIT_21 = nil +-- ffi = nil +-- Lua_has_int32 = nil +-- Lua_has_int64 = nil +-- b, library_name = nil +-------------------------------------------------------------------------------- + +if print_debug_messages then + -- Printing list of abilities of your system + print("Abilities:") + print(" Lua version: "..(is_LuaJIT and "LuaJIT "..(is_LuaJIT_21 and "2.1 " or "2.0 ")..(LuaJIT_arch or "")..(ffi and " with FFI" or " without FFI") or _VERSION)) + print(" Integer bitwise operators: "..(Lua_has_int64 and "int64" or Lua_has_int32 and "int32" or "no")) + print(" 32-bit bitwise library: "..(library_name or "not found")) +end + +-- Selecting the most suitable implementation for given set of abilities +local method, branch +if is_LuaJIT and ffi then + method = "Using 'ffi' library of LuaJIT" + branch = "FFI" +elseif is_LuaJIT then + method = "Using special code for sandboxed LuaJIT (no FFI)" + branch = "LJ" +elseif Lua_has_int64 then + method = "Using native int64 bitwise operators" + branch = "INT64" +elseif Lua_has_int32 then + method = "Using native int32 bitwise operators" + branch = "INT32" +elseif library_name then -- when bitwise library is available (Lua 5.2 with native library "bit32" or Lua 5.1 with external library "bit") + method = "Using '"..library_name.."' library" + branch = "LIB32" +else + method = "Emulating bitwise operators using look-up table" + branch = "EMUL" +end + +if print_debug_messages then + -- Printing the implementation selected to be used on your system + print("Implementation selected:") + print(" "..method) +end + + +-------------------------------------------------------------------------------- +-- BASIC 32-BIT BITWISE FUNCTIONS +-------------------------------------------------------------------------------- + +local AND, OR, XOR, SHL, SHR, ROL, ROR, NOT, NORM, HEX, XOR_BYTE +-- Only low 32 bits of function arguments matter, high bits are ignored +-- The result of all functions (except HEX) is an integer inside "correct range": +-- for "bit" library: (-2^31)..(2^31-1) +-- for "bit32" library: 0..(2^32-1) + +if branch == "FFI" or branch == "LJ" or branch == "LIB32" then + + -- Your system has 32-bit bitwise library (either "bit" or "bit32") + + AND = b.band -- 2 arguments + OR = b.bor -- 2 arguments + XOR = b.bxor -- 2..5 arguments + SHL = b.lshift -- second argument is integer 0..31 + SHR = b.rshift -- second argument is integer 0..31 + ROL = b.rol or b.lrotate -- second argument is integer 0..31 + ROR = b.ror or b.rrotate -- second argument is integer 0..31 + NOT = b.bnot -- only for LuaJIT + NORM = b.tobit -- only for LuaJIT + HEX = b.tohex -- returns string of 8 lowercase hexadecimal digits + assert(AND and OR and XOR and SHL and SHR and ROL and ROR and NOT, "Library '"..library_name.."' is incomplete") + XOR_BYTE = XOR -- XOR of two bytes (0..255) + +elseif branch == "EMUL" then + + -- Emulating 32-bit bitwise operations using 53-bit floating point arithmetic + + function SHL(x, n) + return (x * 2^n) % 2^32 + end + + function SHR(x, n) + x = x % 2^32 / 2^n + return x - x % 1 + end + + function ROL(x, n) + x = x % 2^32 * 2^n + local r = x % 2^32 + return r + (x - r) / 2^32 + end + + function ROR(x, n) + x = x % 2^32 / 2^n + local r = x % 1 + return r * 2^32 + (x - r) + end + + local AND_of_two_bytes = {[0] = 0} -- look-up table (256*256 entries) + local idx = 0 + for y = 0, 127 * 256, 256 do + for x = y, y + 127 do + x = AND_of_two_bytes[x] * 2 + AND_of_two_bytes[idx] = x + AND_of_two_bytes[idx + 1] = x + AND_of_two_bytes[idx + 256] = x + AND_of_two_bytes[idx + 257] = x + 1 + idx = idx + 2 + end + idx = idx + 256 + end + + local function and_or_xor(x, y, operation) + -- operation: nil = AND, 1 = OR, 2 = XOR + local x0 = x % 2^32 + local y0 = y % 2^32 + local rx = x0 % 256 + local ry = y0 % 256 + local res = AND_of_two_bytes[rx + ry * 256] + x = x0 - rx + y = (y0 - ry) / 256 + rx = x % 65536 + ry = y % 256 + res = res + AND_of_two_bytes[rx + ry] * 256 + x = (x - rx) / 256 + y = (y - ry) / 256 + rx = x % 65536 + y % 256 + res = res + AND_of_two_bytes[rx] * 65536 + res = res + AND_of_two_bytes[(x + y - rx) / 256] * 16777216 + if operation then + res = x0 + y0 - operation * res + end + return res + end + + function AND(x, y) + return and_or_xor(x, y) + end + + function OR(x, y) + return and_or_xor(x, y, 1) + end + + function XOR(x, y, z, t, u) -- 2..5 arguments + if z then + if t then + if u then + t = and_or_xor(t, u, 2) + end + z = and_or_xor(z, t, 2) + end + y = and_or_xor(y, z, 2) + end + return and_or_xor(x, y, 2) + end + + function XOR_BYTE(x, y) + return x + y - 2 * AND_of_two_bytes[x + y * 256] + end + +end + +HEX = HEX + or + pcall(string_format, "%x", 2^31) and + function (x) -- returns string of 8 lowercase hexadecimal digits + return string_format("%08x", x % 4294967296) + end + or + function (x) -- for OpenWrt's dialect of Lua + return string_format("%08x", (x + 2^31) % 2^32 - 2^31) + end + +local function XORA5(x, y) + return XOR(x, y or 0xA5A5A5A5) % 4294967296 +end + +local function create_array_of_lanes() + return {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +end + + +-------------------------------------------------------------------------------- +-- CREATING OPTIMIZED INNER LOOP +-------------------------------------------------------------------------------- + +-- Inner loop functions +local sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64, keccak_feed, blake2s_feed_64, blake2b_feed_128, blake3_feed_64 + +-- Arrays of SHA-2 "magic numbers" (in "INT64" and "FFI" branches "*_lo" arrays contain 64-bit values) +local sha2_K_lo, sha2_K_hi, sha2_H_lo, sha2_H_hi, sha3_RC_lo, sha3_RC_hi = {}, {}, {}, {}, {}, {} +local sha2_H_ext256 = {[224] = {}, [256] = sha2_H_hi} +local sha2_H_ext512_lo, sha2_H_ext512_hi = {[384] = {}, [512] = sha2_H_lo}, {[384] = {}, [512] = sha2_H_hi} +local md5_K, md5_sha1_H = {}, {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0} +local md5_next_shift = {0, 0, 0, 0, 0, 0, 0, 0, 28, 25, 26, 27, 0, 0, 10, 9, 11, 12, 0, 15, 16, 17, 18, 0, 20, 22, 23, 21} +local HEX64, lanes_index_base -- defined only for branches that internally use 64-bit integers: "INT64" and "FFI" +local common_W = {} -- temporary table shared between all calculations (to avoid creating new temporary table every time) +local common_W_blake2b, common_W_blake2s, v_for_blake2s_feed_64 = common_W, common_W, {} +local K_lo_modulo, hi_factor, hi_factor_keccak = 4294967296, 0, 0 +local sigma = { + { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, + { 15, 11, 5, 9, 10, 16, 14, 7, 2, 13, 1, 3, 12, 8, 6, 4 }, + { 12, 9, 13, 1, 6, 3, 16, 14, 11, 15, 4, 7, 8, 2, 10, 5 }, + { 8, 10, 4, 2, 14, 13, 12, 15, 3, 7, 6, 11, 5, 1, 16, 9 }, + { 10, 1, 6, 8, 3, 5, 11, 16, 15, 2, 12, 13, 7, 9, 4, 14 }, + { 3, 13, 7, 11, 1, 12, 9, 4, 5, 14, 8, 6, 16, 15, 2, 10 }, + { 13, 6, 2, 16, 15, 14, 5, 11, 1, 8, 7, 4, 10, 3, 9, 12 }, + { 14, 12, 8, 15, 13, 2, 4, 10, 6, 1, 16, 5, 9, 7, 3, 11 }, + { 7, 16, 15, 10, 12, 4, 1, 9, 13, 3, 14, 8, 2, 5, 11, 6 }, + { 11, 3, 9, 5, 8, 7, 2, 6, 16, 12, 10, 15, 4, 13, 14, 1 }, +}; sigma[11], sigma[12] = sigma[1], sigma[2] +local perm_blake3 = { + 1, 3, 4, 11, 13, 10, 12, 6, + 1, 3, 4, 11, 13, 10, + 2, 7, 5, 8, 14, 15, 16, 9, + 2, 7, 5, 8, 14, 15, +} + +local function build_keccak_format(elem) + local keccak_format = {} + for _, size in ipairs{1, 9, 13, 17, 18, 21} do + keccak_format[size] = "<"..string_rep(elem, size) + end + return keccak_format +end + + +if branch == "FFI" then + + local common_W_FFI_int32 = ffi.new("int32_t[?]", 80) -- 64 is enough for SHA256, but 80 is needed for SHA-1 + common_W_blake2s = common_W_FFI_int32 + v_for_blake2s_feed_64 = ffi.new("int32_t[?]", 16) + perm_blake3 = ffi.new("uint8_t[?]", #perm_blake3 + 1, 0, unpack(perm_blake3)) + for j = 1, 10 do + sigma[j] = ffi.new("uint8_t[?]", #sigma[j] + 1, 0, unpack(sigma[j])) + end; sigma[11], sigma[12] = sigma[1], sigma[2] + + + -- SHA256 implementation for "LuaJIT with FFI" branch + + function sha256_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K = common_W_FFI_int32, sha2_K_hi + for pos = offs, offs + size - 1, 64 do + for j = 0, 15 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) -- slow, but doesn't depend on endianness + W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) + end + for j = 16, 63 do + local a, b = W[j-15], W[j-2] + W[j] = NORM( XOR(ROR(a, 7), ROL(a, 14), SHR(a, 3)) + XOR(ROL(b, 15), ROL(b, 13), SHR(b, 10)) + W[j-7] + W[j-16] ) + end + local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for j = 0, 63, 8 do -- Thanks to Peter Cawley for this workaround (unroll the loop to avoid "PHI shuffling too complex" due to PHIs overlap) + local z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j] + K[j+1] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+1] + K[j+2] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+2] + K[j+3] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+3] + K[j+4] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+4] + K[j+5] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+5] + K[j+6] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+6] + K[j+7] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+7] + K[j+8] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + end + H[1], H[2], H[3], H[4] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4]) + H[5], H[6], H[7], H[8] = NORM(e + H[5]), NORM(f + H[6]), NORM(g + H[7]), NORM(h + H[8]) + end + end + + + local common_W_FFI_int64 = ffi.new("int64_t[?]", 80) + common_W_blake2b = common_W_FFI_int64 + local int64 = ffi.typeof"int64_t" + local int32 = ffi.typeof"int32_t" + local uint32 = ffi.typeof"uint32_t" + hi_factor = int64(2^32) + + if is_LuaJIT_21 then -- LuaJIT 2.1 supports bitwise 64-bit operations + + local AND64, OR64, XOR64, NOT64, SHL64, SHR64, ROL64, ROR64 -- introducing synonyms for better code readability + = AND, OR, XOR, NOT, SHL, SHR, ROL, ROR + HEX64 = HEX + + + -- BLAKE2b implementation for "LuaJIT 2.1 + FFI" branch + + do + local v = ffi.new("int64_t[?]", 16) + local W = common_W_blake2b + + local function G(a, b, c, d, k1, k2) + local va, vb, vc, vd = v[a], v[b], v[c], v[d] + va = W[k1] + (va + vb) + vd = ROR64(XOR64(vd, va), 32) + vc = vc + vd + vb = ROR64(XOR64(vb, vc), 24) + va = W[k2] + (va + vb) + vd = ROR64(XOR64(vd, va), 16) + vc = vc + vd + vb = ROL64(XOR64(vb, vc), 1) + v[a], v[b], v[c], v[d] = va, vb, vc, vd + end + + function blake2b_feed_128(H, _, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 128 + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs, offs + size - 1, 128 do + if str then + for j = 1, 16 do + pos = pos + 8 + local a, b, c, d, e, f, g, h = byte(str, pos - 7, pos) + W[j] = XOR64(OR(SHL(h, 24), SHL(g, 16), SHL(f, 8), e) * int64(2^32), uint32(int32(OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a)))) + end + end + v[0x0], v[0x1], v[0x2], v[0x3], v[0x4], v[0x5], v[0x6], v[0x7] = h1, h2, h3, h4, h5, h6, h7, h8 + v[0x8], v[0x9], v[0xA], v[0xB], v[0xD], v[0xE], v[0xF] = sha2_H_lo[1], sha2_H_lo[2], sha2_H_lo[3], sha2_H_lo[4], sha2_H_lo[6], sha2_H_lo[7], sha2_H_lo[8] + bytes_compressed = bytes_compressed + (last_block_size or 128) + v[0xC] = XOR64(sha2_H_lo[5], bytes_compressed) -- t0 = low_8_bytes(bytes_compressed) + -- t1 = high_8_bytes(bytes_compressed) = 0, message length is always below 2^53 bytes + if last_block_size then -- flag f0 + v[0xE] = NOT64(v[0xE]) + end + if is_last_node then -- flag f1 + v[0xF] = NOT64(v[0xF]) + end + for j = 1, 12 do + local row = sigma[j] + G(0, 4, 8, 12, row[ 1], row[ 2]) + G(1, 5, 9, 13, row[ 3], row[ 4]) + G(2, 6, 10, 14, row[ 5], row[ 6]) + G(3, 7, 11, 15, row[ 7], row[ 8]) + G(0, 5, 10, 15, row[ 9], row[10]) + G(1, 6, 11, 12, row[11], row[12]) + G(2, 7, 8, 13, row[13], row[14]) + G(3, 4, 9, 14, row[15], row[16]) + end + h1 = XOR64(h1, v[0x0], v[0x8]) + h2 = XOR64(h2, v[0x1], v[0x9]) + h3 = XOR64(h3, v[0x2], v[0xA]) + h4 = XOR64(h4, v[0x3], v[0xB]) + h5 = XOR64(h5, v[0x4], v[0xC]) + h6 = XOR64(h6, v[0x5], v[0xD]) + h7 = XOR64(h7, v[0x6], v[0xE]) + h8 = XOR64(h8, v[0x7], v[0xF]) + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + return bytes_compressed + end + + end + + + -- SHA-3 implementation for "LuaJIT 2.1 + FFI" branch + + local arr64_t = ffi.typeof"int64_t[?]" + -- lanes array is indexed from 0 + lanes_index_base = 0 + hi_factor_keccak = int64(2^32) + + function create_array_of_lanes() + return arr64_t(30) -- 25 + 5 for temporary usage + end + + function keccak_feed(lanes, _, str, offs, size, block_size_in_bytes) + -- offs >= 0, size >= 0, size is multiple of block_size_in_bytes, block_size_in_bytes is positive multiple of 8 + local RC = sha3_RC_lo + local qwords_qty = SHR(block_size_in_bytes, 3) + for pos = offs, offs + size - 1, block_size_in_bytes do + for j = 0, qwords_qty - 1 do + pos = pos + 8 + local h, g, f, e, d, c, b, a = byte(str, pos - 7, pos) -- slow, but doesn't depend on endianness + lanes[j] = XOR64(lanes[j], OR64(OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) * int64(2^32), uint32(int32(OR(SHL(e, 24), SHL(f, 16), SHL(g, 8), h))))) + end + for round_idx = 1, 24 do + for j = 0, 4 do + lanes[25 + j] = XOR64(lanes[j], lanes[j+5], lanes[j+10], lanes[j+15], lanes[j+20]) + end + local D = XOR64(lanes[25], ROL64(lanes[27], 1)) + lanes[1], lanes[6], lanes[11], lanes[16] = ROL64(XOR64(D, lanes[6]), 44), ROL64(XOR64(D, lanes[16]), 45), ROL64(XOR64(D, lanes[1]), 1), ROL64(XOR64(D, lanes[11]), 10) + lanes[21] = ROL64(XOR64(D, lanes[21]), 2) + D = XOR64(lanes[26], ROL64(lanes[28], 1)) + lanes[2], lanes[7], lanes[12], lanes[22] = ROL64(XOR64(D, lanes[12]), 43), ROL64(XOR64(D, lanes[22]), 61), ROL64(XOR64(D, lanes[7]), 6), ROL64(XOR64(D, lanes[2]), 62) + lanes[17] = ROL64(XOR64(D, lanes[17]), 15) + D = XOR64(lanes[27], ROL64(lanes[29], 1)) + lanes[3], lanes[8], lanes[18], lanes[23] = ROL64(XOR64(D, lanes[18]), 21), ROL64(XOR64(D, lanes[3]), 28), ROL64(XOR64(D, lanes[23]), 56), ROL64(XOR64(D, lanes[8]), 55) + lanes[13] = ROL64(XOR64(D, lanes[13]), 25) + D = XOR64(lanes[28], ROL64(lanes[25], 1)) + lanes[4], lanes[14], lanes[19], lanes[24] = ROL64(XOR64(D, lanes[24]), 14), ROL64(XOR64(D, lanes[19]), 8), ROL64(XOR64(D, lanes[4]), 27), ROL64(XOR64(D, lanes[14]), 39) + lanes[9] = ROL64(XOR64(D, lanes[9]), 20) + D = XOR64(lanes[29], ROL64(lanes[26], 1)) + lanes[5], lanes[10], lanes[15], lanes[20] = ROL64(XOR64(D, lanes[10]), 3), ROL64(XOR64(D, lanes[20]), 18), ROL64(XOR64(D, lanes[5]), 36), ROL64(XOR64(D, lanes[15]), 41) + lanes[0] = XOR64(D, lanes[0]) + lanes[0], lanes[1], lanes[2], lanes[3], lanes[4] = XOR64(lanes[0], AND64(NOT64(lanes[1]), lanes[2]), RC[round_idx]), XOR64(lanes[1], AND64(NOT64(lanes[2]), lanes[3])), XOR64(lanes[2], AND64(NOT64(lanes[3]), lanes[4])), XOR64(lanes[3], AND64(NOT64(lanes[4]), lanes[0])), XOR64(lanes[4], AND64(NOT64(lanes[0]), lanes[1])) + lanes[5], lanes[6], lanes[7], lanes[8], lanes[9] = XOR64(lanes[8], AND64(NOT64(lanes[9]), lanes[5])), XOR64(lanes[9], AND64(NOT64(lanes[5]), lanes[6])), XOR64(lanes[5], AND64(NOT64(lanes[6]), lanes[7])), XOR64(lanes[6], AND64(NOT64(lanes[7]), lanes[8])), XOR64(lanes[7], AND64(NOT64(lanes[8]), lanes[9])) + lanes[10], lanes[11], lanes[12], lanes[13], lanes[14] = XOR64(lanes[11], AND64(NOT64(lanes[12]), lanes[13])), XOR64(lanes[12], AND64(NOT64(lanes[13]), lanes[14])), XOR64(lanes[13], AND64(NOT64(lanes[14]), lanes[10])), XOR64(lanes[14], AND64(NOT64(lanes[10]), lanes[11])), XOR64(lanes[10], AND64(NOT64(lanes[11]), lanes[12])) + lanes[15], lanes[16], lanes[17], lanes[18], lanes[19] = XOR64(lanes[19], AND64(NOT64(lanes[15]), lanes[16])), XOR64(lanes[15], AND64(NOT64(lanes[16]), lanes[17])), XOR64(lanes[16], AND64(NOT64(lanes[17]), lanes[18])), XOR64(lanes[17], AND64(NOT64(lanes[18]), lanes[19])), XOR64(lanes[18], AND64(NOT64(lanes[19]), lanes[15])) + lanes[20], lanes[21], lanes[22], lanes[23], lanes[24] = XOR64(lanes[22], AND64(NOT64(lanes[23]), lanes[24])), XOR64(lanes[23], AND64(NOT64(lanes[24]), lanes[20])), XOR64(lanes[24], AND64(NOT64(lanes[20]), lanes[21])), XOR64(lanes[20], AND64(NOT64(lanes[21]), lanes[22])), XOR64(lanes[21], AND64(NOT64(lanes[22]), lanes[23])) + end + end + end + + + local A5_long = 0xA5A5A5A5 * int64(2^32 + 1) -- It's impossible to use constant 0xA5A5A5A5A5A5A5A5LL because it will raise syntax error on other Lua versions + + function XORA5(long, long2) + return XOR64(long, long2 or A5_long) + end + + + -- SHA512 implementation for "LuaJIT 2.1 + FFI" branch + + function sha512_feed_128(H, _, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 128 + local W, K = common_W_FFI_int64, sha2_K_lo + for pos = offs, offs + size - 1, 128 do + for j = 0, 15 do + pos = pos + 8 + local a, b, c, d, e, f, g, h = byte(str, pos - 7, pos) -- slow, but doesn't depend on endianness + W[j] = OR64(OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) * int64(2^32), uint32(int32(OR(SHL(e, 24), SHL(f, 16), SHL(g, 8), h)))) + end + for j = 16, 79 do + local a, b = W[j-15], W[j-2] + W[j] = XOR64(ROR64(a, 1), ROR64(a, 8), SHR64(a, 7)) + XOR64(ROR64(b, 19), ROL64(b, 3), SHR64(b, 6)) + W[j-7] + W[j-16] + end + local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for j = 0, 79, 8 do + local z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+1] + W[j] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+2] + W[j+1] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+3] + W[j+2] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+4] + W[j+3] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+5] + W[j+4] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+6] + W[j+5] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+7] + W[j+6] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+8] + W[j+7] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + end + H[1] = a + H[1] + H[2] = b + H[2] + H[3] = c + H[3] + H[4] = d + H[4] + H[5] = e + H[5] + H[6] = f + H[6] + H[7] = g + H[7] + H[8] = h + H[8] + end + end + + else -- LuaJIT 2.0 doesn't support 64-bit bitwise operations + + local U = ffi.new("union{int64_t i64; struct{int32_t "..(ffi.abi("le") and "lo, hi" or "hi, lo")..";} i32;}[3]") + -- this array of unions is used for fast splitting int64 into int32_high and int32_low + + -- "xorrific" 64-bit functions :-) + -- int64 input is splitted into two int32 parts, some bitwise 32-bit operations are performed, finally the result is converted to int64 + -- these functions are needed because bit.* functions in LuaJIT 2.0 don't work with int64_t + + local function XORROR64_1(a) + -- return XOR64(ROR64(a, 1), ROR64(a, 8), SHR64(a, 7)) + U[0].i64 = a + local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi + local t_lo = XOR(SHR(a_lo, 1), SHL(a_hi, 31), SHR(a_lo, 8), SHL(a_hi, 24), SHR(a_lo, 7), SHL(a_hi, 25)) + local t_hi = XOR(SHR(a_hi, 1), SHL(a_lo, 31), SHR(a_hi, 8), SHL(a_lo, 24), SHR(a_hi, 7)) + return t_hi * int64(2^32) + uint32(int32(t_lo)) + end + + local function XORROR64_2(b) + -- return XOR64(ROR64(b, 19), ROL64(b, 3), SHR64(b, 6)) + U[0].i64 = b + local b_lo, b_hi = U[0].i32.lo, U[0].i32.hi + local u_lo = XOR(SHR(b_lo, 19), SHL(b_hi, 13), SHL(b_lo, 3), SHR(b_hi, 29), SHR(b_lo, 6), SHL(b_hi, 26)) + local u_hi = XOR(SHR(b_hi, 19), SHL(b_lo, 13), SHL(b_hi, 3), SHR(b_lo, 29), SHR(b_hi, 6)) + return u_hi * int64(2^32) + uint32(int32(u_lo)) + end + + local function XORROR64_3(e) + -- return XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + U[0].i64 = e + local e_lo, e_hi = U[0].i32.lo, U[0].i32.hi + local u_lo = XOR(SHR(e_lo, 14), SHL(e_hi, 18), SHR(e_lo, 18), SHL(e_hi, 14), SHL(e_lo, 23), SHR(e_hi, 9)) + local u_hi = XOR(SHR(e_hi, 14), SHL(e_lo, 18), SHR(e_hi, 18), SHL(e_lo, 14), SHL(e_hi, 23), SHR(e_lo, 9)) + return u_hi * int64(2^32) + uint32(int32(u_lo)) + end + + local function XORROR64_6(a) + -- return XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + U[0].i64 = a + local b_lo, b_hi = U[0].i32.lo, U[0].i32.hi + local u_lo = XOR(SHR(b_lo, 28), SHL(b_hi, 4), SHL(b_lo, 30), SHR(b_hi, 2), SHL(b_lo, 25), SHR(b_hi, 7)) + local u_hi = XOR(SHR(b_hi, 28), SHL(b_lo, 4), SHL(b_hi, 30), SHR(b_lo, 2), SHL(b_hi, 25), SHR(b_lo, 7)) + return u_hi * int64(2^32) + uint32(int32(u_lo)) + end + + local function XORROR64_4(e, f, g) + -- return XOR64(g, AND64(e, XOR64(f, g))) + U[0].i64 = f + U[1].i64 = g + U[2].i64 = e + local f_lo, f_hi = U[0].i32.lo, U[0].i32.hi + local g_lo, g_hi = U[1].i32.lo, U[1].i32.hi + local e_lo, e_hi = U[2].i32.lo, U[2].i32.hi + local result_lo = XOR(g_lo, AND(e_lo, XOR(f_lo, g_lo))) + local result_hi = XOR(g_hi, AND(e_hi, XOR(f_hi, g_hi))) + return result_hi * int64(2^32) + uint32(int32(result_lo)) + end + + local function XORROR64_5(a, b, c) + -- return XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + U[0].i64 = a + U[1].i64 = b + U[2].i64 = c + local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi + local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi + local c_lo, c_hi = U[2].i32.lo, U[2].i32.hi + local result_lo = XOR(AND(XOR(a_lo, b_lo), c_lo), AND(a_lo, b_lo)) + local result_hi = XOR(AND(XOR(a_hi, b_hi), c_hi), AND(a_hi, b_hi)) + return result_hi * int64(2^32) + uint32(int32(result_lo)) + end + + local function XORROR64_7(a, b, m) + -- return ROR64(XOR64(a, b), m), m = 1..31 + U[0].i64 = a + U[1].i64 = b + local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi + local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi + local c_lo, c_hi = XOR(a_lo, b_lo), XOR(a_hi, b_hi) + local t_lo = XOR(SHR(c_lo, m), SHL(c_hi, -m)) + local t_hi = XOR(SHR(c_hi, m), SHL(c_lo, -m)) + return t_hi * int64(2^32) + uint32(int32(t_lo)) + end + + local function XORROR64_8(a, b) + -- return ROL64(XOR64(a, b), 1) + U[0].i64 = a + U[1].i64 = b + local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi + local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi + local c_lo, c_hi = XOR(a_lo, b_lo), XOR(a_hi, b_hi) + local t_lo = XOR(SHL(c_lo, 1), SHR(c_hi, 31)) + local t_hi = XOR(SHL(c_hi, 1), SHR(c_lo, 31)) + return t_hi * int64(2^32) + uint32(int32(t_lo)) + end + + local function XORROR64_9(a, b) + -- return ROR64(XOR64(a, b), 32) + U[0].i64 = a + U[1].i64 = b + local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi + local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi + local t_hi, t_lo = XOR(a_lo, b_lo), XOR(a_hi, b_hi) + return t_hi * int64(2^32) + uint32(int32(t_lo)) + end + + local function XOR64(a, b) + -- return XOR64(a, b) + U[0].i64 = a + U[1].i64 = b + local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi + local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi + local t_lo, t_hi = XOR(a_lo, b_lo), XOR(a_hi, b_hi) + return t_hi * int64(2^32) + uint32(int32(t_lo)) + end + + local function XORROR64_11(a, b, c) + -- return XOR64(a, b, c) + U[0].i64 = a + U[1].i64 = b + U[2].i64 = c + local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi + local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi + local c_lo, c_hi = U[2].i32.lo, U[2].i32.hi + local t_lo, t_hi = XOR(a_lo, b_lo, c_lo), XOR(a_hi, b_hi, c_hi) + return t_hi * int64(2^32) + uint32(int32(t_lo)) + end + + function XORA5(long, long2) + -- return XOR64(long, long2 or 0xA5A5A5A5A5A5A5A5) + U[0].i64 = long + local lo32, hi32 = U[0].i32.lo, U[0].i32.hi + local long2_lo, long2_hi = 0xA5A5A5A5, 0xA5A5A5A5 + if long2 then + U[1].i64 = long2 + long2_lo, long2_hi = U[1].i32.lo, U[1].i32.hi + end + lo32 = XOR(lo32, long2_lo) + hi32 = XOR(hi32, long2_hi) + return hi32 * int64(2^32) + uint32(int32(lo32)) + end + + function HEX64(long) + U[0].i64 = long + return HEX(U[0].i32.hi)..HEX(U[0].i32.lo) + end + + + -- SHA512 implementation for "LuaJIT 2.0 + FFI" branch + + function sha512_feed_128(H, _, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 128 + local W, K = common_W_FFI_int64, sha2_K_lo + for pos = offs, offs + size - 1, 128 do + for j = 0, 15 do + pos = pos + 8 + local a, b, c, d, e, f, g, h = byte(str, pos - 7, pos) -- slow, but doesn't depend on endianness + W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) * int64(2^32) + uint32(int32(OR(SHL(e, 24), SHL(f, 16), SHL(g, 8), h))) + end + for j = 16, 79 do + W[j] = XORROR64_1(W[j-15]) + XORROR64_2(W[j-2]) + W[j-7] + W[j-16] + end + local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for j = 0, 79, 8 do + local z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+1] + W[j] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+2] + W[j+1] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+3] + W[j+2] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+4] + W[j+3] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+5] + W[j+4] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+6] + W[j+5] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+7] + W[j+6] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+8] + W[j+7] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + end + H[1] = a + H[1] + H[2] = b + H[2] + H[3] = c + H[3] + H[4] = d + H[4] + H[5] = e + H[5] + H[6] = f + H[6] + H[7] = g + H[7] + H[8] = h + H[8] + end + end + + + -- BLAKE2b implementation for "LuaJIT 2.0 + FFI" branch + + do + local v = ffi.new("int64_t[?]", 16) + local W = common_W_blake2b + + local function G(a, b, c, d, k1, k2) + local va, vb, vc, vd = v[a], v[b], v[c], v[d] + va = W[k1] + (va + vb) + vd = XORROR64_9(vd, va) + vc = vc + vd + vb = XORROR64_7(vb, vc, 24) + va = W[k2] + (va + vb) + vd = XORROR64_7(vd, va, 16) + vc = vc + vd + vb = XORROR64_8(vb, vc) + v[a], v[b], v[c], v[d] = va, vb, vc, vd + end + + function blake2b_feed_128(H, _, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 128 + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs, offs + size - 1, 128 do + if str then + for j = 1, 16 do + pos = pos + 8 + local a, b, c, d, e, f, g, h = byte(str, pos - 7, pos) + W[j] = XOR64(OR(SHL(h, 24), SHL(g, 16), SHL(f, 8), e) * int64(2^32), uint32(int32(OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a)))) + end + end + v[0x0], v[0x1], v[0x2], v[0x3], v[0x4], v[0x5], v[0x6], v[0x7] = h1, h2, h3, h4, h5, h6, h7, h8 + v[0x8], v[0x9], v[0xA], v[0xB], v[0xD], v[0xE], v[0xF] = sha2_H_lo[1], sha2_H_lo[2], sha2_H_lo[3], sha2_H_lo[4], sha2_H_lo[6], sha2_H_lo[7], sha2_H_lo[8] + bytes_compressed = bytes_compressed + (last_block_size or 128) + v[0xC] = XOR64(sha2_H_lo[5], bytes_compressed) -- t0 = low_8_bytes(bytes_compressed) + -- t1 = high_8_bytes(bytes_compressed) = 0, message length is always below 2^53 bytes + if last_block_size then -- flag f0 + v[0xE] = -1 - v[0xE] + end + if is_last_node then -- flag f1 + v[0xF] = -1 - v[0xF] + end + for j = 1, 12 do + local row = sigma[j] + G(0, 4, 8, 12, row[ 1], row[ 2]) + G(1, 5, 9, 13, row[ 3], row[ 4]) + G(2, 6, 10, 14, row[ 5], row[ 6]) + G(3, 7, 11, 15, row[ 7], row[ 8]) + G(0, 5, 10, 15, row[ 9], row[10]) + G(1, 6, 11, 12, row[11], row[12]) + G(2, 7, 8, 13, row[13], row[14]) + G(3, 4, 9, 14, row[15], row[16]) + end + h1 = XORROR64_11(h1, v[0x0], v[0x8]) + h2 = XORROR64_11(h2, v[0x1], v[0x9]) + h3 = XORROR64_11(h3, v[0x2], v[0xA]) + h4 = XORROR64_11(h4, v[0x3], v[0xB]) + h5 = XORROR64_11(h5, v[0x4], v[0xC]) + h6 = XORROR64_11(h6, v[0x5], v[0xD]) + h7 = XORROR64_11(h7, v[0x6], v[0xE]) + h8 = XORROR64_11(h8, v[0x7], v[0xF]) + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + return bytes_compressed + end + + end + + end + + + -- MD5 implementation for "LuaJIT with FFI" branch + + function md5_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K = common_W_FFI_int32, md5_K + for pos = offs, offs + size - 1, 64 do + for j = 0, 15 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) -- slow, but doesn't depend on endianness + W[j] = OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a) + end + local a, b, c, d = H[1], H[2], H[3], H[4] + for j = 0, 15, 4 do + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+1] + W[j ] + a), 7) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+2] + W[j+1] + a), 12) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+3] + W[j+2] + a), 17) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+4] + W[j+3] + a), 22) + b) + end + for j = 16, 31, 4 do + local g = 5*j + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+1] + W[AND(g + 1, 15)] + a), 5) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+2] + W[AND(g + 6, 15)] + a), 9) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+3] + W[AND(g - 5, 15)] + a), 14) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+4] + W[AND(g , 15)] + a), 20) + b) + end + for j = 32, 47, 4 do + local g = 3*j + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+1] + W[AND(g + 5, 15)] + a), 4) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+2] + W[AND(g + 8, 15)] + a), 11) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+3] + W[AND(g - 5, 15)] + a), 16) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+4] + W[AND(g - 2, 15)] + a), 23) + b) + end + for j = 48, 63, 4 do + local g = 7*j + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+1] + W[AND(g , 15)] + a), 6) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+2] + W[AND(g + 7, 15)] + a), 10) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+3] + W[AND(g - 2, 15)] + a), 15) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+4] + W[AND(g + 5, 15)] + a), 21) + b) + end + H[1], H[2], H[3], H[4] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4]) + end + end + + + -- SHA-1 implementation for "LuaJIT with FFI" branch + + function sha1_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W_FFI_int32 + for pos = offs, offs + size - 1, 64 do + for j = 0, 15 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) -- slow, but doesn't depend on endianness + W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) + end + for j = 16, 79 do + W[j] = ROL(XOR(W[j-3], W[j-8], W[j-14], W[j-16]), 1) + end + local a, b, c, d, e = H[1], H[2], H[3], H[4], H[5] + for j = 0, 19, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j] + 0x5A827999 + e)) -- constant = floor(2^30 * sqrt(2)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+1] + 0x5A827999 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+2] + 0x5A827999 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+3] + 0x5A827999 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+4] + 0x5A827999 + e)) + end + for j = 20, 39, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j] + 0x6ED9EBA1 + e)) -- 2^30 * sqrt(3) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+1] + 0x6ED9EBA1 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+2] + 0x6ED9EBA1 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+3] + 0x6ED9EBA1 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+4] + 0x6ED9EBA1 + e)) + end + for j = 40, 59, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j] + 0x8F1BBCDC + e)) -- 2^30 * sqrt(5) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+1] + 0x8F1BBCDC + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+2] + 0x8F1BBCDC + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+3] + 0x8F1BBCDC + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+4] + 0x8F1BBCDC + e)) + end + for j = 60, 79, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j] + 0xCA62C1D6 + e)) -- 2^30 * sqrt(10) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+1] + 0xCA62C1D6 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+2] + 0xCA62C1D6 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+3] + 0xCA62C1D6 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+4] + 0xCA62C1D6 + e)) + end + H[1], H[2], H[3], H[4], H[5] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4]), NORM(e + H[5]) + end + end + +end + + +if branch == "FFI" and not is_LuaJIT_21 or branch == "LJ" then + + if branch == "FFI" then + local arr32_t = ffi.typeof"int32_t[?]" + + function create_array_of_lanes() + return arr32_t(31) -- 25 + 5 + 1 (due to 1-based indexing) + end + + end + + + -- SHA-3 implementation for "LuaJIT 2.0 + FFI" and "LuaJIT without FFI" branches + + function keccak_feed(lanes_lo, lanes_hi, str, offs, size, block_size_in_bytes) + -- offs >= 0, size >= 0, size is multiple of block_size_in_bytes, block_size_in_bytes is positive multiple of 8 + local RC_lo, RC_hi = sha3_RC_lo, sha3_RC_hi + local qwords_qty = SHR(block_size_in_bytes, 3) + for pos = offs, offs + size - 1, block_size_in_bytes do + for j = 1, qwords_qty do + local a, b, c, d = byte(str, pos + 1, pos + 4) + lanes_lo[j] = XOR(lanes_lo[j], OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a)) + pos = pos + 8 + a, b, c, d = byte(str, pos - 3, pos) + lanes_hi[j] = XOR(lanes_hi[j], OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a)) + end + for round_idx = 1, 24 do + for j = 1, 5 do + lanes_lo[25 + j] = XOR(lanes_lo[j], lanes_lo[j + 5], lanes_lo[j + 10], lanes_lo[j + 15], lanes_lo[j + 20]) + end + for j = 1, 5 do + lanes_hi[25 + j] = XOR(lanes_hi[j], lanes_hi[j + 5], lanes_hi[j + 10], lanes_hi[j + 15], lanes_hi[j + 20]) + end + local D_lo = XOR(lanes_lo[26], SHL(lanes_lo[28], 1), SHR(lanes_hi[28], 31)) + local D_hi = XOR(lanes_hi[26], SHL(lanes_hi[28], 1), SHR(lanes_lo[28], 31)) + lanes_lo[2], lanes_hi[2], lanes_lo[7], lanes_hi[7], lanes_lo[12], lanes_hi[12], lanes_lo[17], lanes_hi[17] = XOR(SHR(XOR(D_lo, lanes_lo[7]), 20), SHL(XOR(D_hi, lanes_hi[7]), 12)), XOR(SHR(XOR(D_hi, lanes_hi[7]), 20), SHL(XOR(D_lo, lanes_lo[7]), 12)), XOR(SHR(XOR(D_lo, lanes_lo[17]), 19), SHL(XOR(D_hi, lanes_hi[17]), 13)), XOR(SHR(XOR(D_hi, lanes_hi[17]), 19), SHL(XOR(D_lo, lanes_lo[17]), 13)), XOR(SHL(XOR(D_lo, lanes_lo[2]), 1), SHR(XOR(D_hi, lanes_hi[2]), 31)), XOR(SHL(XOR(D_hi, lanes_hi[2]), 1), SHR(XOR(D_lo, lanes_lo[2]), 31)), XOR(SHL(XOR(D_lo, lanes_lo[12]), 10), SHR(XOR(D_hi, lanes_hi[12]), 22)), XOR(SHL(XOR(D_hi, lanes_hi[12]), 10), SHR(XOR(D_lo, lanes_lo[12]), 22)) + local L, H = XOR(D_lo, lanes_lo[22]), XOR(D_hi, lanes_hi[22]) + lanes_lo[22], lanes_hi[22] = XOR(SHL(L, 2), SHR(H, 30)), XOR(SHL(H, 2), SHR(L, 30)) + D_lo = XOR(lanes_lo[27], SHL(lanes_lo[29], 1), SHR(lanes_hi[29], 31)) + D_hi = XOR(lanes_hi[27], SHL(lanes_hi[29], 1), SHR(lanes_lo[29], 31)) + lanes_lo[3], lanes_hi[3], lanes_lo[8], lanes_hi[8], lanes_lo[13], lanes_hi[13], lanes_lo[23], lanes_hi[23] = XOR(SHR(XOR(D_lo, lanes_lo[13]), 21), SHL(XOR(D_hi, lanes_hi[13]), 11)), XOR(SHR(XOR(D_hi, lanes_hi[13]), 21), SHL(XOR(D_lo, lanes_lo[13]), 11)), XOR(SHR(XOR(D_lo, lanes_lo[23]), 3), SHL(XOR(D_hi, lanes_hi[23]), 29)), XOR(SHR(XOR(D_hi, lanes_hi[23]), 3), SHL(XOR(D_lo, lanes_lo[23]), 29)), XOR(SHL(XOR(D_lo, lanes_lo[8]), 6), SHR(XOR(D_hi, lanes_hi[8]), 26)), XOR(SHL(XOR(D_hi, lanes_hi[8]), 6), SHR(XOR(D_lo, lanes_lo[8]), 26)), XOR(SHR(XOR(D_lo, lanes_lo[3]), 2), SHL(XOR(D_hi, lanes_hi[3]), 30)), XOR(SHR(XOR(D_hi, lanes_hi[3]), 2), SHL(XOR(D_lo, lanes_lo[3]), 30)) + L, H = XOR(D_lo, lanes_lo[18]), XOR(D_hi, lanes_hi[18]) + lanes_lo[18], lanes_hi[18] = XOR(SHL(L, 15), SHR(H, 17)), XOR(SHL(H, 15), SHR(L, 17)) + D_lo = XOR(lanes_lo[28], SHL(lanes_lo[30], 1), SHR(lanes_hi[30], 31)) + D_hi = XOR(lanes_hi[28], SHL(lanes_hi[30], 1), SHR(lanes_lo[30], 31)) + lanes_lo[4], lanes_hi[4], lanes_lo[9], lanes_hi[9], lanes_lo[19], lanes_hi[19], lanes_lo[24], lanes_hi[24] = XOR(SHL(XOR(D_lo, lanes_lo[19]), 21), SHR(XOR(D_hi, lanes_hi[19]), 11)), XOR(SHL(XOR(D_hi, lanes_hi[19]), 21), SHR(XOR(D_lo, lanes_lo[19]), 11)), XOR(SHL(XOR(D_lo, lanes_lo[4]), 28), SHR(XOR(D_hi, lanes_hi[4]), 4)), XOR(SHL(XOR(D_hi, lanes_hi[4]), 28), SHR(XOR(D_lo, lanes_lo[4]), 4)), XOR(SHR(XOR(D_lo, lanes_lo[24]), 8), SHL(XOR(D_hi, lanes_hi[24]), 24)), XOR(SHR(XOR(D_hi, lanes_hi[24]), 8), SHL(XOR(D_lo, lanes_lo[24]), 24)), XOR(SHR(XOR(D_lo, lanes_lo[9]), 9), SHL(XOR(D_hi, lanes_hi[9]), 23)), XOR(SHR(XOR(D_hi, lanes_hi[9]), 9), SHL(XOR(D_lo, lanes_lo[9]), 23)) + L, H = XOR(D_lo, lanes_lo[14]), XOR(D_hi, lanes_hi[14]) + lanes_lo[14], lanes_hi[14] = XOR(SHL(L, 25), SHR(H, 7)), XOR(SHL(H, 25), SHR(L, 7)) + D_lo = XOR(lanes_lo[29], SHL(lanes_lo[26], 1), SHR(lanes_hi[26], 31)) + D_hi = XOR(lanes_hi[29], SHL(lanes_hi[26], 1), SHR(lanes_lo[26], 31)) + lanes_lo[5], lanes_hi[5], lanes_lo[15], lanes_hi[15], lanes_lo[20], lanes_hi[20], lanes_lo[25], lanes_hi[25] = XOR(SHL(XOR(D_lo, lanes_lo[25]), 14), SHR(XOR(D_hi, lanes_hi[25]), 18)), XOR(SHL(XOR(D_hi, lanes_hi[25]), 14), SHR(XOR(D_lo, lanes_lo[25]), 18)), XOR(SHL(XOR(D_lo, lanes_lo[20]), 8), SHR(XOR(D_hi, lanes_hi[20]), 24)), XOR(SHL(XOR(D_hi, lanes_hi[20]), 8), SHR(XOR(D_lo, lanes_lo[20]), 24)), XOR(SHL(XOR(D_lo, lanes_lo[5]), 27), SHR(XOR(D_hi, lanes_hi[5]), 5)), XOR(SHL(XOR(D_hi, lanes_hi[5]), 27), SHR(XOR(D_lo, lanes_lo[5]), 5)), XOR(SHR(XOR(D_lo, lanes_lo[15]), 25), SHL(XOR(D_hi, lanes_hi[15]), 7)), XOR(SHR(XOR(D_hi, lanes_hi[15]), 25), SHL(XOR(D_lo, lanes_lo[15]), 7)) + L, H = XOR(D_lo, lanes_lo[10]), XOR(D_hi, lanes_hi[10]) + lanes_lo[10], lanes_hi[10] = XOR(SHL(L, 20), SHR(H, 12)), XOR(SHL(H, 20), SHR(L, 12)) + D_lo = XOR(lanes_lo[30], SHL(lanes_lo[27], 1), SHR(lanes_hi[27], 31)) + D_hi = XOR(lanes_hi[30], SHL(lanes_hi[27], 1), SHR(lanes_lo[27], 31)) + lanes_lo[6], lanes_hi[6], lanes_lo[11], lanes_hi[11], lanes_lo[16], lanes_hi[16], lanes_lo[21], lanes_hi[21] = XOR(SHL(XOR(D_lo, lanes_lo[11]), 3), SHR(XOR(D_hi, lanes_hi[11]), 29)), XOR(SHL(XOR(D_hi, lanes_hi[11]), 3), SHR(XOR(D_lo, lanes_lo[11]), 29)), XOR(SHL(XOR(D_lo, lanes_lo[21]), 18), SHR(XOR(D_hi, lanes_hi[21]), 14)), XOR(SHL(XOR(D_hi, lanes_hi[21]), 18), SHR(XOR(D_lo, lanes_lo[21]), 14)), XOR(SHR(XOR(D_lo, lanes_lo[6]), 28), SHL(XOR(D_hi, lanes_hi[6]), 4)), XOR(SHR(XOR(D_hi, lanes_hi[6]), 28), SHL(XOR(D_lo, lanes_lo[6]), 4)), XOR(SHR(XOR(D_lo, lanes_lo[16]), 23), SHL(XOR(D_hi, lanes_hi[16]), 9)), XOR(SHR(XOR(D_hi, lanes_hi[16]), 23), SHL(XOR(D_lo, lanes_lo[16]), 9)) + lanes_lo[1], lanes_hi[1] = XOR(D_lo, lanes_lo[1]), XOR(D_hi, lanes_hi[1]) + lanes_lo[1], lanes_lo[2], lanes_lo[3], lanes_lo[4], lanes_lo[5] = XOR(lanes_lo[1], AND(NOT(lanes_lo[2]), lanes_lo[3]), RC_lo[round_idx]), XOR(lanes_lo[2], AND(NOT(lanes_lo[3]), lanes_lo[4])), XOR(lanes_lo[3], AND(NOT(lanes_lo[4]), lanes_lo[5])), XOR(lanes_lo[4], AND(NOT(lanes_lo[5]), lanes_lo[1])), XOR(lanes_lo[5], AND(NOT(lanes_lo[1]), lanes_lo[2])) + lanes_lo[6], lanes_lo[7], lanes_lo[8], lanes_lo[9], lanes_lo[10] = XOR(lanes_lo[9], AND(NOT(lanes_lo[10]), lanes_lo[6])), XOR(lanes_lo[10], AND(NOT(lanes_lo[6]), lanes_lo[7])), XOR(lanes_lo[6], AND(NOT(lanes_lo[7]), lanes_lo[8])), XOR(lanes_lo[7], AND(NOT(lanes_lo[8]), lanes_lo[9])), XOR(lanes_lo[8], AND(NOT(lanes_lo[9]), lanes_lo[10])) + lanes_lo[11], lanes_lo[12], lanes_lo[13], lanes_lo[14], lanes_lo[15] = XOR(lanes_lo[12], AND(NOT(lanes_lo[13]), lanes_lo[14])), XOR(lanes_lo[13], AND(NOT(lanes_lo[14]), lanes_lo[15])), XOR(lanes_lo[14], AND(NOT(lanes_lo[15]), lanes_lo[11])), XOR(lanes_lo[15], AND(NOT(lanes_lo[11]), lanes_lo[12])), XOR(lanes_lo[11], AND(NOT(lanes_lo[12]), lanes_lo[13])) + lanes_lo[16], lanes_lo[17], lanes_lo[18], lanes_lo[19], lanes_lo[20] = XOR(lanes_lo[20], AND(NOT(lanes_lo[16]), lanes_lo[17])), XOR(lanes_lo[16], AND(NOT(lanes_lo[17]), lanes_lo[18])), XOR(lanes_lo[17], AND(NOT(lanes_lo[18]), lanes_lo[19])), XOR(lanes_lo[18], AND(NOT(lanes_lo[19]), lanes_lo[20])), XOR(lanes_lo[19], AND(NOT(lanes_lo[20]), lanes_lo[16])) + lanes_lo[21], lanes_lo[22], lanes_lo[23], lanes_lo[24], lanes_lo[25] = XOR(lanes_lo[23], AND(NOT(lanes_lo[24]), lanes_lo[25])), XOR(lanes_lo[24], AND(NOT(lanes_lo[25]), lanes_lo[21])), XOR(lanes_lo[25], AND(NOT(lanes_lo[21]), lanes_lo[22])), XOR(lanes_lo[21], AND(NOT(lanes_lo[22]), lanes_lo[23])), XOR(lanes_lo[22], AND(NOT(lanes_lo[23]), lanes_lo[24])) + lanes_hi[1], lanes_hi[2], lanes_hi[3], lanes_hi[4], lanes_hi[5] = XOR(lanes_hi[1], AND(NOT(lanes_hi[2]), lanes_hi[3]), RC_hi[round_idx]), XOR(lanes_hi[2], AND(NOT(lanes_hi[3]), lanes_hi[4])), XOR(lanes_hi[3], AND(NOT(lanes_hi[4]), lanes_hi[5])), XOR(lanes_hi[4], AND(NOT(lanes_hi[5]), lanes_hi[1])), XOR(lanes_hi[5], AND(NOT(lanes_hi[1]), lanes_hi[2])) + lanes_hi[6], lanes_hi[7], lanes_hi[8], lanes_hi[9], lanes_hi[10] = XOR(lanes_hi[9], AND(NOT(lanes_hi[10]), lanes_hi[6])), XOR(lanes_hi[10], AND(NOT(lanes_hi[6]), lanes_hi[7])), XOR(lanes_hi[6], AND(NOT(lanes_hi[7]), lanes_hi[8])), XOR(lanes_hi[7], AND(NOT(lanes_hi[8]), lanes_hi[9])), XOR(lanes_hi[8], AND(NOT(lanes_hi[9]), lanes_hi[10])) + lanes_hi[11], lanes_hi[12], lanes_hi[13], lanes_hi[14], lanes_hi[15] = XOR(lanes_hi[12], AND(NOT(lanes_hi[13]), lanes_hi[14])), XOR(lanes_hi[13], AND(NOT(lanes_hi[14]), lanes_hi[15])), XOR(lanes_hi[14], AND(NOT(lanes_hi[15]), lanes_hi[11])), XOR(lanes_hi[15], AND(NOT(lanes_hi[11]), lanes_hi[12])), XOR(lanes_hi[11], AND(NOT(lanes_hi[12]), lanes_hi[13])) + lanes_hi[16], lanes_hi[17], lanes_hi[18], lanes_hi[19], lanes_hi[20] = XOR(lanes_hi[20], AND(NOT(lanes_hi[16]), lanes_hi[17])), XOR(lanes_hi[16], AND(NOT(lanes_hi[17]), lanes_hi[18])), XOR(lanes_hi[17], AND(NOT(lanes_hi[18]), lanes_hi[19])), XOR(lanes_hi[18], AND(NOT(lanes_hi[19]), lanes_hi[20])), XOR(lanes_hi[19], AND(NOT(lanes_hi[20]), lanes_hi[16])) + lanes_hi[21], lanes_hi[22], lanes_hi[23], lanes_hi[24], lanes_hi[25] = XOR(lanes_hi[23], AND(NOT(lanes_hi[24]), lanes_hi[25])), XOR(lanes_hi[24], AND(NOT(lanes_hi[25]), lanes_hi[21])), XOR(lanes_hi[25], AND(NOT(lanes_hi[21]), lanes_hi[22])), XOR(lanes_hi[21], AND(NOT(lanes_hi[22]), lanes_hi[23])), XOR(lanes_hi[22], AND(NOT(lanes_hi[23]), lanes_hi[24])) + end + end + end + +end + + +if branch == "LJ" then + + + -- SHA256 implementation for "LuaJIT without FFI" branch + + function sha256_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K = common_W, sha2_K_hi + for pos = offs, offs + size - 1, 64 do + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) + end + for j = 17, 64 do + local a, b = W[j-15], W[j-2] + W[j] = NORM( NORM( XOR(ROR(a, 7), ROL(a, 14), SHR(a, 3)) + XOR(ROL(b, 15), ROL(b, 13), SHR(b, 10)) ) + NORM( W[j-7] + W[j-16] ) ) + end + local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for j = 1, 64, 8 do -- Thanks to Peter Cawley for this workaround (unroll the loop to avoid "PHI shuffling too complex" due to PHIs overlap) + local z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j] + W[j] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+1] + W[j+1] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+2] + W[j+2] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+3] + W[j+3] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+4] + W[j+4] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+5] + W[j+5] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+6] + W[j+6] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+7] + W[j+7] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + end + H[1], H[2], H[3], H[4] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4]) + H[5], H[6], H[7], H[8] = NORM(e + H[5]), NORM(f + H[6]), NORM(g + H[7]), NORM(h + H[8]) + end + end + + local function ADD64_4(a_lo, a_hi, b_lo, b_hi, c_lo, c_hi, d_lo, d_hi) + local sum_lo = a_lo % 2^32 + b_lo % 2^32 + c_lo % 2^32 + d_lo % 2^32 + local sum_hi = a_hi + b_hi + c_hi + d_hi + local result_lo = NORM( sum_lo ) + local result_hi = NORM( sum_hi + floor(sum_lo / 2^32) ) + return result_lo, result_hi + end + + if LuaJIT_arch == "x86" then -- Special trick is required to avoid "PHI shuffling too complex" on x86 platform + + + -- SHA512 implementation for "LuaJIT x86 without FFI" branch + + function sha512_feed_128(H_lo, H_hi, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 128 + -- W1_hi, W1_lo, W2_hi, W2_lo, ... Wk_hi = W[2*k-1], Wk_lo = W[2*k] + local W, K_lo, K_hi = common_W, sha2_K_lo, sha2_K_hi + for pos = offs, offs + size - 1, 128 do + for j = 1, 16*2 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) + end + for jj = 17*2, 80*2, 2 do + local a_lo, a_hi = W[jj-30], W[jj-31] + local t_lo = XOR(OR(SHR(a_lo, 1), SHL(a_hi, 31)), OR(SHR(a_lo, 8), SHL(a_hi, 24)), OR(SHR(a_lo, 7), SHL(a_hi, 25))) + local t_hi = XOR(OR(SHR(a_hi, 1), SHL(a_lo, 31)), OR(SHR(a_hi, 8), SHL(a_lo, 24)), SHR(a_hi, 7)) + local b_lo, b_hi = W[jj-4], W[jj-5] + local u_lo = XOR(OR(SHR(b_lo, 19), SHL(b_hi, 13)), OR(SHL(b_lo, 3), SHR(b_hi, 29)), OR(SHR(b_lo, 6), SHL(b_hi, 26))) + local u_hi = XOR(OR(SHR(b_hi, 19), SHL(b_lo, 13)), OR(SHL(b_hi, 3), SHR(b_lo, 29)), SHR(b_hi, 6)) + W[jj], W[jj-1] = ADD64_4(t_lo, t_hi, u_lo, u_hi, W[jj-14], W[jj-15], W[jj-32], W[jj-33]) + end + local a_lo, b_lo, c_lo, d_lo, e_lo, f_lo, g_lo, h_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] + local a_hi, b_hi, c_hi, d_hi, e_hi, f_hi, g_hi, h_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] + local zero = 0 + for j = 1, 80 do + local t_lo = XOR(g_lo, AND(e_lo, XOR(f_lo, g_lo))) + local t_hi = XOR(g_hi, AND(e_hi, XOR(f_hi, g_hi))) + local u_lo = XOR(OR(SHR(e_lo, 14), SHL(e_hi, 18)), OR(SHR(e_lo, 18), SHL(e_hi, 14)), OR(SHL(e_lo, 23), SHR(e_hi, 9))) + local u_hi = XOR(OR(SHR(e_hi, 14), SHL(e_lo, 18)), OR(SHR(e_hi, 18), SHL(e_lo, 14)), OR(SHL(e_hi, 23), SHR(e_lo, 9))) + local sum_lo = u_lo % 2^32 + t_lo % 2^32 + h_lo % 2^32 + K_lo[j] + W[2*j] % 2^32 + local z_lo, z_hi = NORM( sum_lo ), NORM( u_hi + t_hi + h_hi + K_hi[j] + W[2*j-1] + floor(sum_lo / 2^32) ) + zero = zero + zero -- this thick is needed to avoid "PHI shuffling too complex" due to PHIs overlap + h_lo, h_hi, g_lo, g_hi, f_lo, f_hi = OR(zero, g_lo), OR(zero, g_hi), OR(zero, f_lo), OR(zero, f_hi), OR(zero, e_lo), OR(zero, e_hi) + local sum_lo = z_lo % 2^32 + d_lo % 2^32 + e_lo, e_hi = NORM( sum_lo ), NORM( z_hi + d_hi + floor(sum_lo / 2^32) ) + d_lo, d_hi, c_lo, c_hi, b_lo, b_hi = OR(zero, c_lo), OR(zero, c_hi), OR(zero, b_lo), OR(zero, b_hi), OR(zero, a_lo), OR(zero, a_hi) + u_lo = XOR(OR(SHR(b_lo, 28), SHL(b_hi, 4)), OR(SHL(b_lo, 30), SHR(b_hi, 2)), OR(SHL(b_lo, 25), SHR(b_hi, 7))) + u_hi = XOR(OR(SHR(b_hi, 28), SHL(b_lo, 4)), OR(SHL(b_hi, 30), SHR(b_lo, 2)), OR(SHL(b_hi, 25), SHR(b_lo, 7))) + t_lo = OR(AND(d_lo, c_lo), AND(b_lo, XOR(d_lo, c_lo))) + t_hi = OR(AND(d_hi, c_hi), AND(b_hi, XOR(d_hi, c_hi))) + local sum_lo = z_lo % 2^32 + t_lo % 2^32 + u_lo % 2^32 + a_lo, a_hi = NORM( sum_lo ), NORM( z_hi + t_hi + u_hi + floor(sum_lo / 2^32) ) + end + H_lo[1], H_hi[1] = ADD64_4(H_lo[1], H_hi[1], a_lo, a_hi, 0, 0, 0, 0) + H_lo[2], H_hi[2] = ADD64_4(H_lo[2], H_hi[2], b_lo, b_hi, 0, 0, 0, 0) + H_lo[3], H_hi[3] = ADD64_4(H_lo[3], H_hi[3], c_lo, c_hi, 0, 0, 0, 0) + H_lo[4], H_hi[4] = ADD64_4(H_lo[4], H_hi[4], d_lo, d_hi, 0, 0, 0, 0) + H_lo[5], H_hi[5] = ADD64_4(H_lo[5], H_hi[5], e_lo, e_hi, 0, 0, 0, 0) + H_lo[6], H_hi[6] = ADD64_4(H_lo[6], H_hi[6], f_lo, f_hi, 0, 0, 0, 0) + H_lo[7], H_hi[7] = ADD64_4(H_lo[7], H_hi[7], g_lo, g_hi, 0, 0, 0, 0) + H_lo[8], H_hi[8] = ADD64_4(H_lo[8], H_hi[8], h_lo, h_hi, 0, 0, 0, 0) + end + end + + else -- all platforms except x86 + + + -- SHA512 implementation for "LuaJIT non-x86 without FFI" branch + + function sha512_feed_128(H_lo, H_hi, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 128 + -- W1_hi, W1_lo, W2_hi, W2_lo, ... Wk_hi = W[2*k-1], Wk_lo = W[2*k] + local W, K_lo, K_hi = common_W, sha2_K_lo, sha2_K_hi + for pos = offs, offs + size - 1, 128 do + for j = 1, 16*2 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) + end + for jj = 17*2, 80*2, 2 do + local a_lo, a_hi = W[jj-30], W[jj-31] + local t_lo = XOR(OR(SHR(a_lo, 1), SHL(a_hi, 31)), OR(SHR(a_lo, 8), SHL(a_hi, 24)), OR(SHR(a_lo, 7), SHL(a_hi, 25))) + local t_hi = XOR(OR(SHR(a_hi, 1), SHL(a_lo, 31)), OR(SHR(a_hi, 8), SHL(a_lo, 24)), SHR(a_hi, 7)) + local b_lo, b_hi = W[jj-4], W[jj-5] + local u_lo = XOR(OR(SHR(b_lo, 19), SHL(b_hi, 13)), OR(SHL(b_lo, 3), SHR(b_hi, 29)), OR(SHR(b_lo, 6), SHL(b_hi, 26))) + local u_hi = XOR(OR(SHR(b_hi, 19), SHL(b_lo, 13)), OR(SHL(b_hi, 3), SHR(b_lo, 29)), SHR(b_hi, 6)) + W[jj], W[jj-1] = ADD64_4(t_lo, t_hi, u_lo, u_hi, W[jj-14], W[jj-15], W[jj-32], W[jj-33]) + end + local a_lo, b_lo, c_lo, d_lo, e_lo, f_lo, g_lo, h_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] + local a_hi, b_hi, c_hi, d_hi, e_hi, f_hi, g_hi, h_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] + for j = 1, 80 do + local t_lo = XOR(g_lo, AND(e_lo, XOR(f_lo, g_lo))) + local t_hi = XOR(g_hi, AND(e_hi, XOR(f_hi, g_hi))) + local u_lo = XOR(OR(SHR(e_lo, 14), SHL(e_hi, 18)), OR(SHR(e_lo, 18), SHL(e_hi, 14)), OR(SHL(e_lo, 23), SHR(e_hi, 9))) + local u_hi = XOR(OR(SHR(e_hi, 14), SHL(e_lo, 18)), OR(SHR(e_hi, 18), SHL(e_lo, 14)), OR(SHL(e_hi, 23), SHR(e_lo, 9))) + local sum_lo = u_lo % 2^32 + t_lo % 2^32 + h_lo % 2^32 + K_lo[j] + W[2*j] % 2^32 + local z_lo, z_hi = NORM( sum_lo ), NORM( u_hi + t_hi + h_hi + K_hi[j] + W[2*j-1] + floor(sum_lo / 2^32) ) + h_lo, h_hi, g_lo, g_hi, f_lo, f_hi = g_lo, g_hi, f_lo, f_hi, e_lo, e_hi + local sum_lo = z_lo % 2^32 + d_lo % 2^32 + e_lo, e_hi = NORM( sum_lo ), NORM( z_hi + d_hi + floor(sum_lo / 2^32) ) + d_lo, d_hi, c_lo, c_hi, b_lo, b_hi = c_lo, c_hi, b_lo, b_hi, a_lo, a_hi + u_lo = XOR(OR(SHR(b_lo, 28), SHL(b_hi, 4)), OR(SHL(b_lo, 30), SHR(b_hi, 2)), OR(SHL(b_lo, 25), SHR(b_hi, 7))) + u_hi = XOR(OR(SHR(b_hi, 28), SHL(b_lo, 4)), OR(SHL(b_hi, 30), SHR(b_lo, 2)), OR(SHL(b_hi, 25), SHR(b_lo, 7))) + t_lo = OR(AND(d_lo, c_lo), AND(b_lo, XOR(d_lo, c_lo))) + t_hi = OR(AND(d_hi, c_hi), AND(b_hi, XOR(d_hi, c_hi))) + local sum_lo = z_lo % 2^32 + u_lo % 2^32 + t_lo % 2^32 + a_lo, a_hi = NORM( sum_lo ), NORM( z_hi + u_hi + t_hi + floor(sum_lo / 2^32) ) + end + H_lo[1], H_hi[1] = ADD64_4(H_lo[1], H_hi[1], a_lo, a_hi, 0, 0, 0, 0) + H_lo[2], H_hi[2] = ADD64_4(H_lo[2], H_hi[2], b_lo, b_hi, 0, 0, 0, 0) + H_lo[3], H_hi[3] = ADD64_4(H_lo[3], H_hi[3], c_lo, c_hi, 0, 0, 0, 0) + H_lo[4], H_hi[4] = ADD64_4(H_lo[4], H_hi[4], d_lo, d_hi, 0, 0, 0, 0) + H_lo[5], H_hi[5] = ADD64_4(H_lo[5], H_hi[5], e_lo, e_hi, 0, 0, 0, 0) + H_lo[6], H_hi[6] = ADD64_4(H_lo[6], H_hi[6], f_lo, f_hi, 0, 0, 0, 0) + H_lo[7], H_hi[7] = ADD64_4(H_lo[7], H_hi[7], g_lo, g_hi, 0, 0, 0, 0) + H_lo[8], H_hi[8] = ADD64_4(H_lo[8], H_hi[8], h_lo, h_hi, 0, 0, 0, 0) + end + end + + end + + + -- MD5 implementation for "LuaJIT without FFI" branch + + function md5_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K = common_W, md5_K + for pos = offs, offs + size - 1, 64 do + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a) + end + local a, b, c, d = H[1], H[2], H[3], H[4] + for j = 1, 16, 4 do + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j ] + W[j ] + a), 7) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+1] + W[j+1] + a), 12) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+2] + W[j+2] + a), 17) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+3] + W[j+3] + a), 22) + b) + end + for j = 17, 32, 4 do + local g = 5*j-4 + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j ] + W[AND(g , 15) + 1] + a), 5) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+1] + W[AND(g + 5, 15) + 1] + a), 9) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+2] + W[AND(g + 10, 15) + 1] + a), 14) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+3] + W[AND(g - 1, 15) + 1] + a), 20) + b) + end + for j = 33, 48, 4 do + local g = 3*j+2 + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j ] + W[AND(g , 15) + 1] + a), 4) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+1] + W[AND(g + 3, 15) + 1] + a), 11) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+2] + W[AND(g + 6, 15) + 1] + a), 16) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+3] + W[AND(g - 7, 15) + 1] + a), 23) + b) + end + for j = 49, 64, 4 do + local g = j*7 + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j ] + W[AND(g - 7, 15) + 1] + a), 6) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+1] + W[AND(g , 15) + 1] + a), 10) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+2] + W[AND(g + 7, 15) + 1] + a), 15) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+3] + W[AND(g - 2, 15) + 1] + a), 21) + b) + end + H[1], H[2], H[3], H[4] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4]) + end + end + + + -- SHA-1 implementation for "LuaJIT without FFI" branch + + function sha1_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W + for pos = offs, offs + size - 1, 64 do + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) + end + for j = 17, 80 do + W[j] = ROL(XOR(W[j-3], W[j-8], W[j-14], W[j-16]), 1) + end + local a, b, c, d, e = H[1], H[2], H[3], H[4], H[5] + for j = 1, 20, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j] + 0x5A827999 + e)) -- constant = floor(2^30 * sqrt(2)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+1] + 0x5A827999 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+2] + 0x5A827999 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+3] + 0x5A827999 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+4] + 0x5A827999 + e)) + end + for j = 21, 40, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j] + 0x6ED9EBA1 + e)) -- 2^30 * sqrt(3) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+1] + 0x6ED9EBA1 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+2] + 0x6ED9EBA1 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+3] + 0x6ED9EBA1 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+4] + 0x6ED9EBA1 + e)) + end + for j = 41, 60, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j] + 0x8F1BBCDC + e)) -- 2^30 * sqrt(5) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+1] + 0x8F1BBCDC + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+2] + 0x8F1BBCDC + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+3] + 0x8F1BBCDC + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+4] + 0x8F1BBCDC + e)) + end + for j = 61, 80, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j] + 0xCA62C1D6 + e)) -- 2^30 * sqrt(10) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+1] + 0xCA62C1D6 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+2] + 0xCA62C1D6 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+3] + 0xCA62C1D6 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+4] + 0xCA62C1D6 + e)) + end + H[1], H[2], H[3], H[4], H[5] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4]), NORM(e + H[5]) + end + end + + + -- BLAKE2b implementation for "LuaJIT without FFI" branch + + do + local v_lo, v_hi = {}, {} + + local function G(a, b, c, d, k1, k2) + local W = common_W + local va_lo, vb_lo, vc_lo, vd_lo = v_lo[a], v_lo[b], v_lo[c], v_lo[d] + local va_hi, vb_hi, vc_hi, vd_hi = v_hi[a], v_hi[b], v_hi[c], v_hi[d] + local z = W[2*k1-1] + (va_lo % 2^32 + vb_lo % 2^32) + va_lo = NORM(z) + va_hi = NORM(W[2*k1] + (va_hi + vb_hi + floor(z / 2^32))) + vd_lo, vd_hi = XOR(vd_hi, va_hi), XOR(vd_lo, va_lo) + z = vc_lo % 2^32 + vd_lo % 2^32 + vc_lo = NORM(z) + vc_hi = NORM(vc_hi + vd_hi + floor(z / 2^32)) + vb_lo, vb_hi = XOR(vb_lo, vc_lo), XOR(vb_hi, vc_hi) + vb_lo, vb_hi = XOR(SHR(vb_lo, 24), SHL(vb_hi, 8)), XOR(SHR(vb_hi, 24), SHL(vb_lo, 8)) + z = W[2*k2-1] + (va_lo % 2^32 + vb_lo % 2^32) + va_lo = NORM(z) + va_hi = NORM(W[2*k2] + (va_hi + vb_hi + floor(z / 2^32))) + vd_lo, vd_hi = XOR(vd_lo, va_lo), XOR(vd_hi, va_hi) + vd_lo, vd_hi = XOR(SHR(vd_lo, 16), SHL(vd_hi, 16)), XOR(SHR(vd_hi, 16), SHL(vd_lo, 16)) + z = vc_lo % 2^32 + vd_lo % 2^32 + vc_lo = NORM(z) + vc_hi = NORM(vc_hi + vd_hi + floor(z / 2^32)) + vb_lo, vb_hi = XOR(vb_lo, vc_lo), XOR(vb_hi, vc_hi) + vb_lo, vb_hi = XOR(SHL(vb_lo, 1), SHR(vb_hi, 31)), XOR(SHL(vb_hi, 1), SHR(vb_lo, 31)) + v_lo[a], v_lo[b], v_lo[c], v_lo[d] = va_lo, vb_lo, vc_lo, vd_lo + v_hi[a], v_hi[b], v_hi[c], v_hi[d] = va_hi, vb_hi, vc_hi, vd_hi + end + + function blake2b_feed_128(H_lo, H_hi, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 128 + local W = common_W + local h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] + local h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] + for pos = offs, offs + size - 1, 128 do + if str then + for j = 1, 32 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = d * 2^24 + OR(SHL(c, 16), SHL(b, 8), a) + end + end + v_lo[0x0], v_lo[0x1], v_lo[0x2], v_lo[0x3], v_lo[0x4], v_lo[0x5], v_lo[0x6], v_lo[0x7] = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + v_lo[0x8], v_lo[0x9], v_lo[0xA], v_lo[0xB], v_lo[0xC], v_lo[0xD], v_lo[0xE], v_lo[0xF] = sha2_H_lo[1], sha2_H_lo[2], sha2_H_lo[3], sha2_H_lo[4], sha2_H_lo[5], sha2_H_lo[6], sha2_H_lo[7], sha2_H_lo[8] + v_hi[0x0], v_hi[0x1], v_hi[0x2], v_hi[0x3], v_hi[0x4], v_hi[0x5], v_hi[0x6], v_hi[0x7] = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + v_hi[0x8], v_hi[0x9], v_hi[0xA], v_hi[0xB], v_hi[0xC], v_hi[0xD], v_hi[0xE], v_hi[0xF] = sha2_H_hi[1], sha2_H_hi[2], sha2_H_hi[3], sha2_H_hi[4], sha2_H_hi[5], sha2_H_hi[6], sha2_H_hi[7], sha2_H_hi[8] + bytes_compressed = bytes_compressed + (last_block_size or 128) + local t0_lo = bytes_compressed % 2^32 + local t0_hi = floor(bytes_compressed / 2^32) + v_lo[0xC] = XOR(v_lo[0xC], t0_lo) -- t0 = low_8_bytes(bytes_compressed) + v_hi[0xC] = XOR(v_hi[0xC], t0_hi) + -- t1 = high_8_bytes(bytes_compressed) = 0, message length is always below 2^53 bytes + if last_block_size then -- flag f0 + v_lo[0xE] = NOT(v_lo[0xE]) + v_hi[0xE] = NOT(v_hi[0xE]) + end + if is_last_node then -- flag f1 + v_lo[0xF] = NOT(v_lo[0xF]) + v_hi[0xF] = NOT(v_hi[0xF]) + end + for j = 1, 12 do + local row = sigma[j] + G(0, 4, 8, 12, row[ 1], row[ 2]) + G(1, 5, 9, 13, row[ 3], row[ 4]) + G(2, 6, 10, 14, row[ 5], row[ 6]) + G(3, 7, 11, 15, row[ 7], row[ 8]) + G(0, 5, 10, 15, row[ 9], row[10]) + G(1, 6, 11, 12, row[11], row[12]) + G(2, 7, 8, 13, row[13], row[14]) + G(3, 4, 9, 14, row[15], row[16]) + end + h1_lo = XOR(h1_lo, v_lo[0x0], v_lo[0x8]) + h2_lo = XOR(h2_lo, v_lo[0x1], v_lo[0x9]) + h3_lo = XOR(h3_lo, v_lo[0x2], v_lo[0xA]) + h4_lo = XOR(h4_lo, v_lo[0x3], v_lo[0xB]) + h5_lo = XOR(h5_lo, v_lo[0x4], v_lo[0xC]) + h6_lo = XOR(h6_lo, v_lo[0x5], v_lo[0xD]) + h7_lo = XOR(h7_lo, v_lo[0x6], v_lo[0xE]) + h8_lo = XOR(h8_lo, v_lo[0x7], v_lo[0xF]) + h1_hi = XOR(h1_hi, v_hi[0x0], v_hi[0x8]) + h2_hi = XOR(h2_hi, v_hi[0x1], v_hi[0x9]) + h3_hi = XOR(h3_hi, v_hi[0x2], v_hi[0xA]) + h4_hi = XOR(h4_hi, v_hi[0x3], v_hi[0xB]) + h5_hi = XOR(h5_hi, v_hi[0x4], v_hi[0xC]) + h6_hi = XOR(h6_hi, v_hi[0x5], v_hi[0xD]) + h7_hi = XOR(h7_hi, v_hi[0x6], v_hi[0xE]) + h8_hi = XOR(h8_hi, v_hi[0x7], v_hi[0xF]) + end + H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] = h1_lo % 2^32, h2_lo % 2^32, h3_lo % 2^32, h4_lo % 2^32, h5_lo % 2^32, h6_lo % 2^32, h7_lo % 2^32, h8_lo % 2^32 + H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] = h1_hi % 2^32, h2_hi % 2^32, h3_hi % 2^32, h4_hi % 2^32, h5_hi % 2^32, h6_hi % 2^32, h7_hi % 2^32, h8_hi % 2^32 + return bytes_compressed + end + + end +end + + +if branch == "FFI" or branch == "LJ" then + + + -- BLAKE2s and BLAKE3 implementations for "LuaJIT with FFI" and "LuaJIT without FFI" branches + + do + local W = common_W_blake2s + local v = v_for_blake2s_feed_64 + + local function G(a, b, c, d, k1, k2) + local va, vb, vc, vd = v[a], v[b], v[c], v[d] + va = NORM(W[k1] + (va + vb)) + vd = ROR(XOR(vd, va), 16) + vc = NORM(vc + vd) + vb = ROR(XOR(vb, vc), 12) + va = NORM(W[k2] + (va + vb)) + vd = ROR(XOR(vd, va), 8) + vc = NORM(vc + vd) + vb = ROR(XOR(vb, vc), 7) + v[a], v[b], v[c], v[d] = va, vb, vc, vd + end + + function blake2s_feed_64(H, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 64 + local h1, h2, h3, h4, h5, h6, h7, h8 = NORM(H[1]), NORM(H[2]), NORM(H[3]), NORM(H[4]), NORM(H[5]), NORM(H[6]), NORM(H[7]), NORM(H[8]) + for pos = offs, offs + size - 1, 64 do + if str then + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a) + end + end + v[0x0], v[0x1], v[0x2], v[0x3], v[0x4], v[0x5], v[0x6], v[0x7] = h1, h2, h3, h4, h5, h6, h7, h8 + v[0x8], v[0x9], v[0xA], v[0xB], v[0xE], v[0xF] = NORM(sha2_H_hi[1]), NORM(sha2_H_hi[2]), NORM(sha2_H_hi[3]), NORM(sha2_H_hi[4]), NORM(sha2_H_hi[7]), NORM(sha2_H_hi[8]) + bytes_compressed = bytes_compressed + (last_block_size or 64) + local t0 = bytes_compressed % 2^32 + local t1 = floor(bytes_compressed / 2^32) + v[0xC] = XOR(sha2_H_hi[5], t0) -- t0 = low_4_bytes(bytes_compressed) + v[0xD] = XOR(sha2_H_hi[6], t1) -- t1 = high_4_bytes(bytes_compressed + if last_block_size then -- flag f0 + v[0xE] = NOT(v[0xE]) + end + if is_last_node then -- flag f1 + v[0xF] = NOT(v[0xF]) + end + for j = 1, 10 do + local row = sigma[j] + G(0, 4, 8, 12, row[ 1], row[ 2]) + G(1, 5, 9, 13, row[ 3], row[ 4]) + G(2, 6, 10, 14, row[ 5], row[ 6]) + G(3, 7, 11, 15, row[ 7], row[ 8]) + G(0, 5, 10, 15, row[ 9], row[10]) + G(1, 6, 11, 12, row[11], row[12]) + G(2, 7, 8, 13, row[13], row[14]) + G(3, 4, 9, 14, row[15], row[16]) + end + h1 = XOR(h1, v[0x0], v[0x8]) + h2 = XOR(h2, v[0x1], v[0x9]) + h3 = XOR(h3, v[0x2], v[0xA]) + h4 = XOR(h4, v[0x3], v[0xB]) + h5 = XOR(h5, v[0x4], v[0xC]) + h6 = XOR(h6, v[0x5], v[0xD]) + h7 = XOR(h7, v[0x6], v[0xE]) + h8 = XOR(h8, v[0x7], v[0xF]) + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + return bytes_compressed + end + + function blake3_feed_64(str, offs, size, flags, chunk_index, H_in, H_out, wide_output, block_length) + -- offs >= 0, size >= 0, size is multiple of 64 + block_length = block_length or 64 + local h1, h2, h3, h4, h5, h6, h7, h8 = NORM(H_in[1]), NORM(H_in[2]), NORM(H_in[3]), NORM(H_in[4]), NORM(H_in[5]), NORM(H_in[6]), NORM(H_in[7]), NORM(H_in[8]) + H_out = H_out or H_in + for pos = offs, offs + size - 1, 64 do + if str then + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a) + end + end + v[0x0], v[0x1], v[0x2], v[0x3], v[0x4], v[0x5], v[0x6], v[0x7] = h1, h2, h3, h4, h5, h6, h7, h8 + v[0x8], v[0x9], v[0xA], v[0xB] = NORM(sha2_H_hi[1]), NORM(sha2_H_hi[2]), NORM(sha2_H_hi[3]), NORM(sha2_H_hi[4]) + v[0xC] = NORM(chunk_index % 2^32) -- t0 = low_4_bytes(chunk_index) + v[0xD] = floor(chunk_index / 2^32) -- t1 = high_4_bytes(chunk_index) + v[0xE], v[0xF] = block_length, flags + for j = 1, 7 do + G(0, 4, 8, 12, perm_blake3[j], perm_blake3[j + 14]) + G(1, 5, 9, 13, perm_blake3[j + 1], perm_blake3[j + 2]) + G(2, 6, 10, 14, perm_blake3[j + 16], perm_blake3[j + 7]) + G(3, 7, 11, 15, perm_blake3[j + 15], perm_blake3[j + 17]) + G(0, 5, 10, 15, perm_blake3[j + 21], perm_blake3[j + 5]) + G(1, 6, 11, 12, perm_blake3[j + 3], perm_blake3[j + 6]) + G(2, 7, 8, 13, perm_blake3[j + 4], perm_blake3[j + 18]) + G(3, 4, 9, 14, perm_blake3[j + 19], perm_blake3[j + 20]) + end + if wide_output then + H_out[ 9] = XOR(h1, v[0x8]) + H_out[10] = XOR(h2, v[0x9]) + H_out[11] = XOR(h3, v[0xA]) + H_out[12] = XOR(h4, v[0xB]) + H_out[13] = XOR(h5, v[0xC]) + H_out[14] = XOR(h6, v[0xD]) + H_out[15] = XOR(h7, v[0xE]) + H_out[16] = XOR(h8, v[0xF]) + end + h1 = XOR(v[0x0], v[0x8]) + h2 = XOR(v[0x1], v[0x9]) + h3 = XOR(v[0x2], v[0xA]) + h4 = XOR(v[0x3], v[0xB]) + h5 = XOR(v[0x4], v[0xC]) + h6 = XOR(v[0x5], v[0xD]) + h7 = XOR(v[0x6], v[0xE]) + h8 = XOR(v[0x7], v[0xF]) + end + H_out[1], H_out[2], H_out[3], H_out[4], H_out[5], H_out[6], H_out[7], H_out[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + + end + +end + + +if branch == "INT64" then + + + -- implementation for Lua 5.3/5.4 + + hi_factor = 4294967296 + hi_factor_keccak = 4294967296 + lanes_index_base = 1 + + HEX64, XORA5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64, keccak_feed, blake2s_feed_64, blake2b_feed_128, blake3_feed_64 = load[=[-- branch "INT64" + local md5_next_shift, md5_K, sha2_K_lo, sha2_K_hi, build_keccak_format, sha3_RC_lo, sigma, common_W, sha2_H_lo, sha2_H_hi, perm_blake3 = ... + local string_format, string_unpack = string.format, string.unpack + + local function HEX64(x) + return string_format("%016x", x) + end + + local function XORA5(x, y) + return x ~ (y or 0xa5a5a5a5a5a5a5a5) + end + + local function XOR_BYTE(x, y) + return x ~ y + end + + local function sha256_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K = common_W, sha2_K_hi + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs + 1, offs + size, 64 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack(">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4", str, pos) + for j = 17, 64 do + local a = W[j-15] + a = a<<32 | a + local b = W[j-2] + b = b<<32 | b + W[j] = (a>>7 ~ a>>18 ~ a>>35) + (b>>17 ~ b>>19 ~ b>>42) + W[j-7] + W[j-16] & (1<<32)-1 + end + local a, b, c, d, e, f, g, h = h1, h2, h3, h4, h5, h6, h7, h8 + for j = 1, 64 do + e = e<<32 | e & (1<<32)-1 + local z = (e>>6 ~ e>>11 ~ e>>25) + (g ~ e & (f ~ g)) + h + K[j] + W[j] + h = g + g = f + f = e + e = z + d + d = c + c = b + b = a + a = a<<32 | a & (1<<32)-1 + a = z + ((a ~ c) & d ~ a & c) + (a>>2 ~ a>>13 ~ a>>22) + end + h1 = a + h1 + h2 = b + h2 + h3 = c + h3 + h4 = d + h4 + h5 = e + h5 + h6 = f + h6 + h7 = g + h7 + h8 = h + h8 + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + + local function sha512_feed_128(H, _, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 128 + local W, K = common_W, sha2_K_lo + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs + 1, offs + size, 128 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack(">i8i8i8i8i8i8i8i8i8i8i8i8i8i8i8i8", str, pos) + for j = 17, 80 do + local a = W[j-15] + local b = W[j-2] + W[j] = (a >> 1 ~ a >> 7 ~ a >> 8 ~ a << 56 ~ a << 63) + (b >> 6 ~ b >> 19 ~ b >> 61 ~ b << 3 ~ b << 45) + W[j-7] + W[j-16] + end + local a, b, c, d, e, f, g, h = h1, h2, h3, h4, h5, h6, h7, h8 + for j = 1, 80 do + local z = (e >> 14 ~ e >> 18 ~ e >> 41 ~ e << 23 ~ e << 46 ~ e << 50) + (g ~ e & (f ~ g)) + h + K[j] + W[j] + h = g + g = f + f = e + e = z + d + d = c + c = b + b = a + a = z + ((a ~ c) & d ~ a & c) + (a >> 28 ~ a >> 34 ~ a >> 39 ~ a << 25 ~ a << 30 ~ a << 36) + end + h1 = a + h1 + h2 = b + h2 + h3 = c + h3 + h4 = d + h4 + h5 = e + h5 + h6 = f + h6 + h7 = g + h7 + h8 = h + h8 + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + + local function md5_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K, md5_next_shift = common_W, md5_K, md5_next_shift + local h1, h2, h3, h4 = H[1], H[2], H[3], H[4] + for pos = offs + 1, offs + size, 64 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack("> s) + b + s = md5_next_shift[s] + end + s = 32-5 + for j = 17, 32 do + local F = (c ~ d & (b ~ c)) + a + K[j] + W[(5*j-4 & 15) + 1] + a = d + d = c + c = b + b = ((F<<32 | F & (1<<32)-1) >> s) + b + s = md5_next_shift[s] + end + s = 32-4 + for j = 33, 48 do + local F = (b ~ c ~ d) + a + K[j] + W[(3*j+2 & 15) + 1] + a = d + d = c + c = b + b = ((F<<32 | F & (1<<32)-1) >> s) + b + s = md5_next_shift[s] + end + s = 32-6 + for j = 49, 64 do + local F = (c ~ (b | ~d)) + a + K[j] + W[(j*7-7 & 15) + 1] + a = d + d = c + c = b + b = ((F<<32 | F & (1<<32)-1) >> s) + b + s = md5_next_shift[s] + end + h1 = a + h1 + h2 = b + h2 + h3 = c + h3 + h4 = d + h4 + end + H[1], H[2], H[3], H[4] = h1, h2, h3, h4 + end + + local function sha1_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W + local h1, h2, h3, h4, h5 = H[1], H[2], H[3], H[4], H[5] + for pos = offs + 1, offs + size, 64 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack(">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4", str, pos) + for j = 17, 80 do + local a = W[j-3] ~ W[j-8] ~ W[j-14] ~ W[j-16] + W[j] = (a<<32 | a) << 1 >> 32 + end + local a, b, c, d, e = h1, h2, h3, h4, h5 + for j = 1, 20 do + local z = ((a<<32 | a & (1<<32)-1) >> 27) + (d ~ b & (c ~ d)) + 0x5A827999 + W[j] + e -- constant = floor(2^30 * sqrt(2)) + e = d + d = c + c = (b<<32 | b & (1<<32)-1) >> 2 + b = a + a = z + end + for j = 21, 40 do + local z = ((a<<32 | a & (1<<32)-1) >> 27) + (b ~ c ~ d) + 0x6ED9EBA1 + W[j] + e -- 2^30 * sqrt(3) + e = d + d = c + c = (b<<32 | b & (1<<32)-1) >> 2 + b = a + a = z + end + for j = 41, 60 do + local z = ((a<<32 | a & (1<<32)-1) >> 27) + ((b ~ c) & d ~ b & c) + 0x8F1BBCDC + W[j] + e -- 2^30 * sqrt(5) + e = d + d = c + c = (b<<32 | b & (1<<32)-1) >> 2 + b = a + a = z + end + for j = 61, 80 do + local z = ((a<<32 | a & (1<<32)-1) >> 27) + (b ~ c ~ d) + 0xCA62C1D6 + W[j] + e -- 2^30 * sqrt(10) + e = d + d = c + c = (b<<32 | b & (1<<32)-1) >> 2 + b = a + a = z + end + h1 = a + h1 + h2 = b + h2 + h3 = c + h3 + h4 = d + h4 + h5 = e + h5 + end + H[1], H[2], H[3], H[4], H[5] = h1, h2, h3, h4, h5 + end + + local keccak_format_i8 = build_keccak_format("i8") + + local function keccak_feed(lanes, _, str, offs, size, block_size_in_bytes) + -- offs >= 0, size >= 0, size is multiple of block_size_in_bytes, block_size_in_bytes is positive multiple of 8 + local RC = sha3_RC_lo + local qwords_qty = block_size_in_bytes / 8 + local keccak_format = keccak_format_i8[qwords_qty] + for pos = offs + 1, offs + size, block_size_in_bytes do + local qwords_from_message = {string_unpack(keccak_format, str, pos)} + for j = 1, qwords_qty do + lanes[j] = lanes[j] ~ qwords_from_message[j] + end + local L01, L02, L03, L04, L05, L06, L07, L08, L09, L10, L11, L12, L13, L14, L15, L16, L17, L18, L19, L20, L21, L22, L23, L24, L25 = + lanes[1], lanes[2], lanes[3], lanes[4], lanes[5], lanes[6], lanes[7], lanes[8], lanes[9], lanes[10], lanes[11], lanes[12], lanes[13], + lanes[14], lanes[15], lanes[16], lanes[17], lanes[18], lanes[19], lanes[20], lanes[21], lanes[22], lanes[23], lanes[24], lanes[25] + for round_idx = 1, 24 do + local C1 = L01 ~ L06 ~ L11 ~ L16 ~ L21 + local C2 = L02 ~ L07 ~ L12 ~ L17 ~ L22 + local C3 = L03 ~ L08 ~ L13 ~ L18 ~ L23 + local C4 = L04 ~ L09 ~ L14 ~ L19 ~ L24 + local C5 = L05 ~ L10 ~ L15 ~ L20 ~ L25 + local D = C1 ~ C3<<1 ~ C3>>63 + local T0 = D ~ L02 + local T1 = D ~ L07 + local T2 = D ~ L12 + local T3 = D ~ L17 + local T4 = D ~ L22 + L02 = T1<<44 ~ T1>>20 + L07 = T3<<45 ~ T3>>19 + L12 = T0<<1 ~ T0>>63 + L17 = T2<<10 ~ T2>>54 + L22 = T4<<2 ~ T4>>62 + D = C2 ~ C4<<1 ~ C4>>63 + T0 = D ~ L03 + T1 = D ~ L08 + T2 = D ~ L13 + T3 = D ~ L18 + T4 = D ~ L23 + L03 = T2<<43 ~ T2>>21 + L08 = T4<<61 ~ T4>>3 + L13 = T1<<6 ~ T1>>58 + L18 = T3<<15 ~ T3>>49 + L23 = T0<<62 ~ T0>>2 + D = C3 ~ C5<<1 ~ C5>>63 + T0 = D ~ L04 + T1 = D ~ L09 + T2 = D ~ L14 + T3 = D ~ L19 + T4 = D ~ L24 + L04 = T3<<21 ~ T3>>43 + L09 = T0<<28 ~ T0>>36 + L14 = T2<<25 ~ T2>>39 + L19 = T4<<56 ~ T4>>8 + L24 = T1<<55 ~ T1>>9 + D = C4 ~ C1<<1 ~ C1>>63 + T0 = D ~ L05 + T1 = D ~ L10 + T2 = D ~ L15 + T3 = D ~ L20 + T4 = D ~ L25 + L05 = T4<<14 ~ T4>>50 + L10 = T1<<20 ~ T1>>44 + L15 = T3<<8 ~ T3>>56 + L20 = T0<<27 ~ T0>>37 + L25 = T2<<39 ~ T2>>25 + D = C5 ~ C2<<1 ~ C2>>63 + T1 = D ~ L06 + T2 = D ~ L11 + T3 = D ~ L16 + T4 = D ~ L21 + L06 = T2<<3 ~ T2>>61 + L11 = T4<<18 ~ T4>>46 + L16 = T1<<36 ~ T1>>28 + L21 = T3<<41 ~ T3>>23 + L01 = D ~ L01 + L01, L02, L03, L04, L05 = L01 ~ ~L02 & L03, L02 ~ ~L03 & L04, L03 ~ ~L04 & L05, L04 ~ ~L05 & L01, L05 ~ ~L01 & L02 + L06, L07, L08, L09, L10 = L09 ~ ~L10 & L06, L10 ~ ~L06 & L07, L06 ~ ~L07 & L08, L07 ~ ~L08 & L09, L08 ~ ~L09 & L10 + L11, L12, L13, L14, L15 = L12 ~ ~L13 & L14, L13 ~ ~L14 & L15, L14 ~ ~L15 & L11, L15 ~ ~L11 & L12, L11 ~ ~L12 & L13 + L16, L17, L18, L19, L20 = L20 ~ ~L16 & L17, L16 ~ ~L17 & L18, L17 ~ ~L18 & L19, L18 ~ ~L19 & L20, L19 ~ ~L20 & L16 + L21, L22, L23, L24, L25 = L23 ~ ~L24 & L25, L24 ~ ~L25 & L21, L25 ~ ~L21 & L22, L21 ~ ~L22 & L23, L22 ~ ~L23 & L24 + L01 = L01 ~ RC[round_idx] + end + lanes[1] = L01 + lanes[2] = L02 + lanes[3] = L03 + lanes[4] = L04 + lanes[5] = L05 + lanes[6] = L06 + lanes[7] = L07 + lanes[8] = L08 + lanes[9] = L09 + lanes[10] = L10 + lanes[11] = L11 + lanes[12] = L12 + lanes[13] = L13 + lanes[14] = L14 + lanes[15] = L15 + lanes[16] = L16 + lanes[17] = L17 + lanes[18] = L18 + lanes[19] = L19 + lanes[20] = L20 + lanes[21] = L21 + lanes[22] = L22 + lanes[23] = L23 + lanes[24] = L24 + lanes[25] = L25 + end + end + + local function blake2s_feed_64(H, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs + 1, offs + size, 64 do + if str then + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack("> 32 -- t1 = high_4_bytes(bytes_compressed) + if last_block_size then -- flag f0 + vE = ~vE + end + if is_last_node then -- flag f1 + vF = ~vF + end + for j = 1, 10 do + local row = sigma[j] + v0 = v0 + v4 + W[row[1]] + vC = vC ~ v0 + vC = (vC & (1<<32)-1) >> 16 | vC << 16 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = (v4 & (1<<32)-1) >> 12 | v4 << 20 + v0 = v0 + v4 + W[row[2]] + vC = vC ~ v0 + vC = (vC & (1<<32)-1) >> 8 | vC << 24 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = (v4 & (1<<32)-1) >> 7 | v4 << 25 + v1 = v1 + v5 + W[row[3]] + vD = vD ~ v1 + vD = (vD & (1<<32)-1) >> 16 | vD << 16 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = (v5 & (1<<32)-1) >> 12 | v5 << 20 + v1 = v1 + v5 + W[row[4]] + vD = vD ~ v1 + vD = (vD & (1<<32)-1) >> 8 | vD << 24 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = (v5 & (1<<32)-1) >> 7 | v5 << 25 + v2 = v2 + v6 + W[row[5]] + vE = vE ~ v2 + vE = (vE & (1<<32)-1) >> 16 | vE << 16 + vA = vA + vE + v6 = v6 ~ vA + v6 = (v6 & (1<<32)-1) >> 12 | v6 << 20 + v2 = v2 + v6 + W[row[6]] + vE = vE ~ v2 + vE = (vE & (1<<32)-1) >> 8 | vE << 24 + vA = vA + vE + v6 = v6 ~ vA + v6 = (v6 & (1<<32)-1) >> 7 | v6 << 25 + v3 = v3 + v7 + W[row[7]] + vF = vF ~ v3 + vF = (vF & (1<<32)-1) >> 16 | vF << 16 + vB = vB + vF + v7 = v7 ~ vB + v7 = (v7 & (1<<32)-1) >> 12 | v7 << 20 + v3 = v3 + v7 + W[row[8]] + vF = vF ~ v3 + vF = (vF & (1<<32)-1) >> 8 | vF << 24 + vB = vB + vF + v7 = v7 ~ vB + v7 = (v7 & (1<<32)-1) >> 7 | v7 << 25 + v0 = v0 + v5 + W[row[9]] + vF = vF ~ v0 + vF = (vF & (1<<32)-1) >> 16 | vF << 16 + vA = vA + vF + v5 = v5 ~ vA + v5 = (v5 & (1<<32)-1) >> 12 | v5 << 20 + v0 = v0 + v5 + W[row[10]] + vF = vF ~ v0 + vF = (vF & (1<<32)-1) >> 8 | vF << 24 + vA = vA + vF + v5 = v5 ~ vA + v5 = (v5 & (1<<32)-1) >> 7 | v5 << 25 + v1 = v1 + v6 + W[row[11]] + vC = vC ~ v1 + vC = (vC & (1<<32)-1) >> 16 | vC << 16 + vB = vB + vC + v6 = v6 ~ vB + v6 = (v6 & (1<<32)-1) >> 12 | v6 << 20 + v1 = v1 + v6 + W[row[12]] + vC = vC ~ v1 + vC = (vC & (1<<32)-1) >> 8 | vC << 24 + vB = vB + vC + v6 = v6 ~ vB + v6 = (v6 & (1<<32)-1) >> 7 | v6 << 25 + v2 = v2 + v7 + W[row[13]] + vD = vD ~ v2 + vD = (vD & (1<<32)-1) >> 16 | vD << 16 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = (v7 & (1<<32)-1) >> 12 | v7 << 20 + v2 = v2 + v7 + W[row[14]] + vD = vD ~ v2 + vD = (vD & (1<<32)-1) >> 8 | vD << 24 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = (v7 & (1<<32)-1) >> 7 | v7 << 25 + v3 = v3 + v4 + W[row[15]] + vE = vE ~ v3 + vE = (vE & (1<<32)-1) >> 16 | vE << 16 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = (v4 & (1<<32)-1) >> 12 | v4 << 20 + v3 = v3 + v4 + W[row[16]] + vE = vE ~ v3 + vE = (vE & (1<<32)-1) >> 8 | vE << 24 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = (v4 & (1<<32)-1) >> 7 | v4 << 25 + end + h1 = h1 ~ v0 ~ v8 + h2 = h2 ~ v1 ~ v9 + h3 = h3 ~ v2 ~ vA + h4 = h4 ~ v3 ~ vB + h5 = h5 ~ v4 ~ vC + h6 = h6 ~ v5 ~ vD + h7 = h7 ~ v6 ~ vE + h8 = h8 ~ v7 ~ vF + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + return bytes_compressed + end + + local function blake2b_feed_128(H, _, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 128 + local W = common_W + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs + 1, offs + size, 128 do + if str then + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack("> 32 | vC << 32 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = v4 >> 24 | v4 << 40 + v0 = v0 + v4 + W[row[2]] + vC = vC ~ v0 + vC = vC >> 16 | vC << 48 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = v4 >> 63 | v4 << 1 + v1 = v1 + v5 + W[row[3]] + vD = vD ~ v1 + vD = vD >> 32 | vD << 32 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = v5 >> 24 | v5 << 40 + v1 = v1 + v5 + W[row[4]] + vD = vD ~ v1 + vD = vD >> 16 | vD << 48 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = v5 >> 63 | v5 << 1 + v2 = v2 + v6 + W[row[5]] + vE = vE ~ v2 + vE = vE >> 32 | vE << 32 + vA = vA + vE + v6 = v6 ~ vA + v6 = v6 >> 24 | v6 << 40 + v2 = v2 + v6 + W[row[6]] + vE = vE ~ v2 + vE = vE >> 16 | vE << 48 + vA = vA + vE + v6 = v6 ~ vA + v6 = v6 >> 63 | v6 << 1 + v3 = v3 + v7 + W[row[7]] + vF = vF ~ v3 + vF = vF >> 32 | vF << 32 + vB = vB + vF + v7 = v7 ~ vB + v7 = v7 >> 24 | v7 << 40 + v3 = v3 + v7 + W[row[8]] + vF = vF ~ v3 + vF = vF >> 16 | vF << 48 + vB = vB + vF + v7 = v7 ~ vB + v7 = v7 >> 63 | v7 << 1 + v0 = v0 + v5 + W[row[9]] + vF = vF ~ v0 + vF = vF >> 32 | vF << 32 + vA = vA + vF + v5 = v5 ~ vA + v5 = v5 >> 24 | v5 << 40 + v0 = v0 + v5 + W[row[10]] + vF = vF ~ v0 + vF = vF >> 16 | vF << 48 + vA = vA + vF + v5 = v5 ~ vA + v5 = v5 >> 63 | v5 << 1 + v1 = v1 + v6 + W[row[11]] + vC = vC ~ v1 + vC = vC >> 32 | vC << 32 + vB = vB + vC + v6 = v6 ~ vB + v6 = v6 >> 24 | v6 << 40 + v1 = v1 + v6 + W[row[12]] + vC = vC ~ v1 + vC = vC >> 16 | vC << 48 + vB = vB + vC + v6 = v6 ~ vB + v6 = v6 >> 63 | v6 << 1 + v2 = v2 + v7 + W[row[13]] + vD = vD ~ v2 + vD = vD >> 32 | vD << 32 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = v7 >> 24 | v7 << 40 + v2 = v2 + v7 + W[row[14]] + vD = vD ~ v2 + vD = vD >> 16 | vD << 48 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = v7 >> 63 | v7 << 1 + v3 = v3 + v4 + W[row[15]] + vE = vE ~ v3 + vE = vE >> 32 | vE << 32 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = v4 >> 24 | v4 << 40 + v3 = v3 + v4 + W[row[16]] + vE = vE ~ v3 + vE = vE >> 16 | vE << 48 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = v4 >> 63 | v4 << 1 + end + h1 = h1 ~ v0 ~ v8 + h2 = h2 ~ v1 ~ v9 + h3 = h3 ~ v2 ~ vA + h4 = h4 ~ v3 ~ vB + h5 = h5 ~ v4 ~ vC + h6 = h6 ~ v5 ~ vD + h7 = h7 ~ v6 ~ vE + h8 = h8 ~ v7 ~ vF + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + return bytes_compressed + end + + local function blake3_feed_64(str, offs, size, flags, chunk_index, H_in, H_out, wide_output, block_length) + -- offs >= 0, size >= 0, size is multiple of 64 + block_length = block_length or 64 + local W = common_W + local h1, h2, h3, h4, h5, h6, h7, h8 = H_in[1], H_in[2], H_in[3], H_in[4], H_in[5], H_in[6], H_in[7], H_in[8] + H_out = H_out or H_in + for pos = offs + 1, offs + size, 64 do + if str then + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack("> 16 | vC << 16 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = (v4 & (1<<32)-1) >> 12 | v4 << 20 + v0 = v0 + v4 + W[perm_blake3[j + 14]] + vC = vC ~ v0 + vC = (vC & (1<<32)-1) >> 8 | vC << 24 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = (v4 & (1<<32)-1) >> 7 | v4 << 25 + v1 = v1 + v5 + W[perm_blake3[j + 1]] + vD = vD ~ v1 + vD = (vD & (1<<32)-1) >> 16 | vD << 16 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = (v5 & (1<<32)-1) >> 12 | v5 << 20 + v1 = v1 + v5 + W[perm_blake3[j + 2]] + vD = vD ~ v1 + vD = (vD & (1<<32)-1) >> 8 | vD << 24 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = (v5 & (1<<32)-1) >> 7 | v5 << 25 + v2 = v2 + v6 + W[perm_blake3[j + 16]] + vE = vE ~ v2 + vE = (vE & (1<<32)-1) >> 16 | vE << 16 + vA = vA + vE + v6 = v6 ~ vA + v6 = (v6 & (1<<32)-1) >> 12 | v6 << 20 + v2 = v2 + v6 + W[perm_blake3[j + 7]] + vE = vE ~ v2 + vE = (vE & (1<<32)-1) >> 8 | vE << 24 + vA = vA + vE + v6 = v6 ~ vA + v6 = (v6 & (1<<32)-1) >> 7 | v6 << 25 + v3 = v3 + v7 + W[perm_blake3[j + 15]] + vF = vF ~ v3 + vF = (vF & (1<<32)-1) >> 16 | vF << 16 + vB = vB + vF + v7 = v7 ~ vB + v7 = (v7 & (1<<32)-1) >> 12 | v7 << 20 + v3 = v3 + v7 + W[perm_blake3[j + 17]] + vF = vF ~ v3 + vF = (vF & (1<<32)-1) >> 8 | vF << 24 + vB = vB + vF + v7 = v7 ~ vB + v7 = (v7 & (1<<32)-1) >> 7 | v7 << 25 + v0 = v0 + v5 + W[perm_blake3[j + 21]] + vF = vF ~ v0 + vF = (vF & (1<<32)-1) >> 16 | vF << 16 + vA = vA + vF + v5 = v5 ~ vA + v5 = (v5 & (1<<32)-1) >> 12 | v5 << 20 + v0 = v0 + v5 + W[perm_blake3[j + 5]] + vF = vF ~ v0 + vF = (vF & (1<<32)-1) >> 8 | vF << 24 + vA = vA + vF + v5 = v5 ~ vA + v5 = (v5 & (1<<32)-1) >> 7 | v5 << 25 + v1 = v1 + v6 + W[perm_blake3[j + 3]] + vC = vC ~ v1 + vC = (vC & (1<<32)-1) >> 16 | vC << 16 + vB = vB + vC + v6 = v6 ~ vB + v6 = (v6 & (1<<32)-1) >> 12 | v6 << 20 + v1 = v1 + v6 + W[perm_blake3[j + 6]] + vC = vC ~ v1 + vC = (vC & (1<<32)-1) >> 8 | vC << 24 + vB = vB + vC + v6 = v6 ~ vB + v6 = (v6 & (1<<32)-1) >> 7 | v6 << 25 + v2 = v2 + v7 + W[perm_blake3[j + 4]] + vD = vD ~ v2 + vD = (vD & (1<<32)-1) >> 16 | vD << 16 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = (v7 & (1<<32)-1) >> 12 | v7 << 20 + v2 = v2 + v7 + W[perm_blake3[j + 18]] + vD = vD ~ v2 + vD = (vD & (1<<32)-1) >> 8 | vD << 24 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = (v7 & (1<<32)-1) >> 7 | v7 << 25 + v3 = v3 + v4 + W[perm_blake3[j + 19]] + vE = vE ~ v3 + vE = (vE & (1<<32)-1) >> 16 | vE << 16 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = (v4 & (1<<32)-1) >> 12 | v4 << 20 + v3 = v3 + v4 + W[perm_blake3[j + 20]] + vE = vE ~ v3 + vE = (vE & (1<<32)-1) >> 8 | vE << 24 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = (v4 & (1<<32)-1) >> 7 | v4 << 25 + end + if wide_output then + H_out[ 9] = h1 ~ v8 + H_out[10] = h2 ~ v9 + H_out[11] = h3 ~ vA + H_out[12] = h4 ~ vB + H_out[13] = h5 ~ vC + H_out[14] = h6 ~ vD + H_out[15] = h7 ~ vE + H_out[16] = h8 ~ vF + end + h1 = v0 ~ v8 + h2 = v1 ~ v9 + h3 = v2 ~ vA + h4 = v3 ~ vB + h5 = v4 ~ vC + h6 = v5 ~ vD + h7 = v6 ~ vE + h8 = v7 ~ vF + end + H_out[1], H_out[2], H_out[3], H_out[4], H_out[5], H_out[6], H_out[7], H_out[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + + return HEX64, XORA5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64, keccak_feed, blake2s_feed_64, blake2b_feed_128, blake3_feed_64 + ]=](md5_next_shift, md5_K, sha2_K_lo, sha2_K_hi, build_keccak_format, sha3_RC_lo, sigma, common_W, sha2_H_lo, sha2_H_hi, perm_blake3) + +end + + +if branch == "INT32" then + + + -- implementation for Lua 5.3/5.4 having non-standard numbers config "int32"+"double" (built with LUA_INT_TYPE=LUA_INT_INT) + + K_lo_modulo = 2^32 + + function HEX(x) -- returns string of 8 lowercase hexadecimal digits + return string_format("%08x", x) + end + + XORA5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64, keccak_feed, blake2s_feed_64, blake2b_feed_128, blake3_feed_64 = load[=[-- branch "INT32" + local md5_next_shift, md5_K, sha2_K_lo, sha2_K_hi, build_keccak_format, sha3_RC_lo, sha3_RC_hi, sigma, common_W, sha2_H_lo, sha2_H_hi, perm_blake3 = ... + local string_unpack, floor = string.unpack, math.floor + + local function XORA5(x, y) + return x ~ (y and (y + 2^31) % 2^32 - 2^31 or 0xA5A5A5A5) + end + + local function XOR_BYTE(x, y) + return x ~ y + end + + local function sha256_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K = common_W, sha2_K_hi + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs + 1, offs + size, 64 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack(">i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4", str, pos) + for j = 17, 64 do + local a, b = W[j-15], W[j-2] + W[j] = (a>>7 ~ a<<25 ~ a<<14 ~ a>>18 ~ a>>3) + (b<<15 ~ b>>17 ~ b<<13 ~ b>>19 ~ b>>10) + W[j-7] + W[j-16] + end + local a, b, c, d, e, f, g, h = h1, h2, h3, h4, h5, h6, h7, h8 + for j = 1, 64 do + local z = (e>>6 ~ e<<26 ~ e>>11 ~ e<<21 ~ e>>25 ~ e<<7) + (g ~ e & (f ~ g)) + h + K[j] + W[j] + h = g + g = f + f = e + e = z + d + d = c + c = b + b = a + a = z + ((a ~ c) & d ~ a & c) + (a>>2 ~ a<<30 ~ a>>13 ~ a<<19 ~ a<<10 ~ a>>22) + end + h1 = a + h1 + h2 = b + h2 + h3 = c + h3 + h4 = d + h4 + h5 = e + h5 + h6 = f + h6 + h7 = g + h7 + h8 = h + h8 + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + + local function sha512_feed_128(H_lo, H_hi, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 128 + -- W1_hi, W1_lo, W2_hi, W2_lo, ... Wk_hi = W[2*k-1], Wk_lo = W[2*k] + local floor, W, K_lo, K_hi = floor, common_W, sha2_K_lo, sha2_K_hi + local h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] + local h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] + for pos = offs + 1, offs + size, 128 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16], + W[17], W[18], W[19], W[20], W[21], W[22], W[23], W[24], W[25], W[26], W[27], W[28], W[29], W[30], W[31], W[32] = + string_unpack(">i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4", str, pos) + for jj = 17*2, 80*2, 2 do + local a_lo, a_hi, b_lo, b_hi = W[jj-30], W[jj-31], W[jj-4], W[jj-5] + local tmp = + (a_lo>>1 ~ a_hi<<31 ~ a_lo>>8 ~ a_hi<<24 ~ a_lo>>7 ~ a_hi<<25) % 2^32 + + (b_lo>>19 ~ b_hi<<13 ~ b_lo<<3 ~ b_hi>>29 ~ b_lo>>6 ~ b_hi<<26) % 2^32 + + W[jj-14] % 2^32 + W[jj-32] % 2^32 + W[jj-1] = + (a_hi>>1 ~ a_lo<<31 ~ a_hi>>8 ~ a_lo<<24 ~ a_hi>>7) + + (b_hi>>19 ~ b_lo<<13 ~ b_hi<<3 ~ b_lo>>29 ~ b_hi>>6) + + W[jj-15] + W[jj-33] + floor(tmp / 2^32) + W[jj] = 0|((tmp + 2^31) % 2^32 - 2^31) + end + local a_lo, b_lo, c_lo, d_lo, e_lo, f_lo, g_lo, h_lo = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + local a_hi, b_hi, c_hi, d_hi, e_hi, f_hi, g_hi, h_hi = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + for j = 1, 80 do + local jj = 2*j + local z_lo = (e_lo>>14 ~ e_hi<<18 ~ e_lo>>18 ~ e_hi<<14 ~ e_lo<<23 ~ e_hi>>9) % 2^32 + (g_lo ~ e_lo & (f_lo ~ g_lo)) % 2^32 + h_lo % 2^32 + K_lo[j] + W[jj] % 2^32 + local z_hi = (e_hi>>14 ~ e_lo<<18 ~ e_hi>>18 ~ e_lo<<14 ~ e_hi<<23 ~ e_lo>>9) + (g_hi ~ e_hi & (f_hi ~ g_hi)) + h_hi + K_hi[j] + W[jj-1] + floor(z_lo / 2^32) + z_lo = z_lo % 2^32 + h_lo = g_lo; h_hi = g_hi + g_lo = f_lo; g_hi = f_hi + f_lo = e_lo; f_hi = e_hi + e_lo = z_lo + d_lo % 2^32 + e_hi = z_hi + d_hi + floor(e_lo / 2^32) + e_lo = 0|((e_lo + 2^31) % 2^32 - 2^31) + d_lo = c_lo; d_hi = c_hi + c_lo = b_lo; c_hi = b_hi + b_lo = a_lo; b_hi = a_hi + z_lo = z_lo + (d_lo & c_lo ~ b_lo & (d_lo ~ c_lo)) % 2^32 + (b_lo>>28 ~ b_hi<<4 ~ b_lo<<30 ~ b_hi>>2 ~ b_lo<<25 ~ b_hi>>7) % 2^32 + a_hi = z_hi + (d_hi & c_hi ~ b_hi & (d_hi ~ c_hi)) + (b_hi>>28 ~ b_lo<<4 ~ b_hi<<30 ~ b_lo>>2 ~ b_hi<<25 ~ b_lo>>7) + floor(z_lo / 2^32) + a_lo = 0|((z_lo + 2^31) % 2^32 - 2^31) + end + a_lo = h1_lo % 2^32 + a_lo % 2^32 + h1_hi = h1_hi + a_hi + floor(a_lo / 2^32) + h1_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + a_lo = h2_lo % 2^32 + b_lo % 2^32 + h2_hi = h2_hi + b_hi + floor(a_lo / 2^32) + h2_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + a_lo = h3_lo % 2^32 + c_lo % 2^32 + h3_hi = h3_hi + c_hi + floor(a_lo / 2^32) + h3_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + a_lo = h4_lo % 2^32 + d_lo % 2^32 + h4_hi = h4_hi + d_hi + floor(a_lo / 2^32) + h4_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + a_lo = h5_lo % 2^32 + e_lo % 2^32 + h5_hi = h5_hi + e_hi + floor(a_lo / 2^32) + h5_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + a_lo = h6_lo % 2^32 + f_lo % 2^32 + h6_hi = h6_hi + f_hi + floor(a_lo / 2^32) + h6_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + a_lo = h7_lo % 2^32 + g_lo % 2^32 + h7_hi = h7_hi + g_hi + floor(a_lo / 2^32) + h7_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + a_lo = h8_lo % 2^32 + h_lo % 2^32 + h8_hi = h8_hi + h_hi + floor(a_lo / 2^32) + h8_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + end + H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + end + + local function md5_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K, md5_next_shift = common_W, md5_K, md5_next_shift + local h1, h2, h3, h4 = H[1], H[2], H[3], H[4] + for pos = offs + 1, offs + size, 64 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack(">s) + b + s = md5_next_shift[s] + end + s = 32-5 + for j = 17, 32 do + local F = (c ~ d & (b ~ c)) + a + K[j] + W[(5*j-4 & 15) + 1] + a = d + d = c + c = b + b = (F << 32-s | F>>s) + b + s = md5_next_shift[s] + end + s = 32-4 + for j = 33, 48 do + local F = (b ~ c ~ d) + a + K[j] + W[(3*j+2 & 15) + 1] + a = d + d = c + c = b + b = (F << 32-s | F>>s) + b + s = md5_next_shift[s] + end + s = 32-6 + for j = 49, 64 do + local F = (c ~ (b | ~d)) + a + K[j] + W[(j*7-7 & 15) + 1] + a = d + d = c + c = b + b = (F << 32-s | F>>s) + b + s = md5_next_shift[s] + end + h1 = a + h1 + h2 = b + h2 + h3 = c + h3 + h4 = d + h4 + end + H[1], H[2], H[3], H[4] = h1, h2, h3, h4 + end + + local function sha1_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W + local h1, h2, h3, h4, h5 = H[1], H[2], H[3], H[4], H[5] + for pos = offs + 1, offs + size, 64 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack(">i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4", str, pos) + for j = 17, 80 do + local a = W[j-3] ~ W[j-8] ~ W[j-14] ~ W[j-16] + W[j] = a << 1 ~ a >> 31 + end + local a, b, c, d, e = h1, h2, h3, h4, h5 + for j = 1, 20 do + local z = (a << 5 ~ a >> 27) + (d ~ b & (c ~ d)) + 0x5A827999 + W[j] + e -- constant = floor(2^30 * sqrt(2)) + e = d + d = c + c = b << 30 ~ b >> 2 + b = a + a = z + end + for j = 21, 40 do + local z = (a << 5 ~ a >> 27) + (b ~ c ~ d) + 0x6ED9EBA1 + W[j] + e -- 2^30 * sqrt(3) + e = d + d = c + c = b << 30 ~ b >> 2 + b = a + a = z + end + for j = 41, 60 do + local z = (a << 5 ~ a >> 27) + ((b ~ c) & d ~ b & c) + 0x8F1BBCDC + W[j] + e -- 2^30 * sqrt(5) + e = d + d = c + c = b << 30 ~ b >> 2 + b = a + a = z + end + for j = 61, 80 do + local z = (a << 5 ~ a >> 27) + (b ~ c ~ d) + 0xCA62C1D6 + W[j] + e -- 2^30 * sqrt(10) + e = d + d = c + c = b << 30 ~ b >> 2 + b = a + a = z + end + h1 = a + h1 + h2 = b + h2 + h3 = c + h3 + h4 = d + h4 + h5 = e + h5 + end + H[1], H[2], H[3], H[4], H[5] = h1, h2, h3, h4, h5 + end + + local keccak_format_i4i4 = build_keccak_format("i4i4") + + local function keccak_feed(lanes_lo, lanes_hi, str, offs, size, block_size_in_bytes) + -- offs >= 0, size >= 0, size is multiple of block_size_in_bytes, block_size_in_bytes is positive multiple of 8 + local RC_lo, RC_hi = sha3_RC_lo, sha3_RC_hi + local qwords_qty = block_size_in_bytes / 8 + local keccak_format = keccak_format_i4i4[qwords_qty] + for pos = offs + 1, offs + size, block_size_in_bytes do + local dwords_from_message = {string_unpack(keccak_format, str, pos)} + for j = 1, qwords_qty do + lanes_lo[j] = lanes_lo[j] ~ dwords_from_message[2*j-1] + lanes_hi[j] = lanes_hi[j] ~ dwords_from_message[2*j] + end + local L01_lo, L01_hi, L02_lo, L02_hi, L03_lo, L03_hi, L04_lo, L04_hi, L05_lo, L05_hi, L06_lo, L06_hi, L07_lo, L07_hi, L08_lo, L08_hi, + L09_lo, L09_hi, L10_lo, L10_hi, L11_lo, L11_hi, L12_lo, L12_hi, L13_lo, L13_hi, L14_lo, L14_hi, L15_lo, L15_hi, L16_lo, L16_hi, + L17_lo, L17_hi, L18_lo, L18_hi, L19_lo, L19_hi, L20_lo, L20_hi, L21_lo, L21_hi, L22_lo, L22_hi, L23_lo, L23_hi, L24_lo, L24_hi, L25_lo, L25_hi = + lanes_lo[1], lanes_hi[1], lanes_lo[2], lanes_hi[2], lanes_lo[3], lanes_hi[3], lanes_lo[4], lanes_hi[4], lanes_lo[5], lanes_hi[5], + lanes_lo[6], lanes_hi[6], lanes_lo[7], lanes_hi[7], lanes_lo[8], lanes_hi[8], lanes_lo[9], lanes_hi[9], lanes_lo[10], lanes_hi[10], + lanes_lo[11], lanes_hi[11], lanes_lo[12], lanes_hi[12], lanes_lo[13], lanes_hi[13], lanes_lo[14], lanes_hi[14], lanes_lo[15], lanes_hi[15], + lanes_lo[16], lanes_hi[16], lanes_lo[17], lanes_hi[17], lanes_lo[18], lanes_hi[18], lanes_lo[19], lanes_hi[19], lanes_lo[20], lanes_hi[20], + lanes_lo[21], lanes_hi[21], lanes_lo[22], lanes_hi[22], lanes_lo[23], lanes_hi[23], lanes_lo[24], lanes_hi[24], lanes_lo[25], lanes_hi[25] + for round_idx = 1, 24 do + local C1_lo = L01_lo ~ L06_lo ~ L11_lo ~ L16_lo ~ L21_lo + local C1_hi = L01_hi ~ L06_hi ~ L11_hi ~ L16_hi ~ L21_hi + local C2_lo = L02_lo ~ L07_lo ~ L12_lo ~ L17_lo ~ L22_lo + local C2_hi = L02_hi ~ L07_hi ~ L12_hi ~ L17_hi ~ L22_hi + local C3_lo = L03_lo ~ L08_lo ~ L13_lo ~ L18_lo ~ L23_lo + local C3_hi = L03_hi ~ L08_hi ~ L13_hi ~ L18_hi ~ L23_hi + local C4_lo = L04_lo ~ L09_lo ~ L14_lo ~ L19_lo ~ L24_lo + local C4_hi = L04_hi ~ L09_hi ~ L14_hi ~ L19_hi ~ L24_hi + local C5_lo = L05_lo ~ L10_lo ~ L15_lo ~ L20_lo ~ L25_lo + local C5_hi = L05_hi ~ L10_hi ~ L15_hi ~ L20_hi ~ L25_hi + local D_lo = C1_lo ~ C3_lo<<1 ~ C3_hi>>31 + local D_hi = C1_hi ~ C3_hi<<1 ~ C3_lo>>31 + local T0_lo = D_lo ~ L02_lo + local T0_hi = D_hi ~ L02_hi + local T1_lo = D_lo ~ L07_lo + local T1_hi = D_hi ~ L07_hi + local T2_lo = D_lo ~ L12_lo + local T2_hi = D_hi ~ L12_hi + local T3_lo = D_lo ~ L17_lo + local T3_hi = D_hi ~ L17_hi + local T4_lo = D_lo ~ L22_lo + local T4_hi = D_hi ~ L22_hi + L02_lo = T1_lo>>20 ~ T1_hi<<12 + L02_hi = T1_hi>>20 ~ T1_lo<<12 + L07_lo = T3_lo>>19 ~ T3_hi<<13 + L07_hi = T3_hi>>19 ~ T3_lo<<13 + L12_lo = T0_lo<<1 ~ T0_hi>>31 + L12_hi = T0_hi<<1 ~ T0_lo>>31 + L17_lo = T2_lo<<10 ~ T2_hi>>22 + L17_hi = T2_hi<<10 ~ T2_lo>>22 + L22_lo = T4_lo<<2 ~ T4_hi>>30 + L22_hi = T4_hi<<2 ~ T4_lo>>30 + D_lo = C2_lo ~ C4_lo<<1 ~ C4_hi>>31 + D_hi = C2_hi ~ C4_hi<<1 ~ C4_lo>>31 + T0_lo = D_lo ~ L03_lo + T0_hi = D_hi ~ L03_hi + T1_lo = D_lo ~ L08_lo + T1_hi = D_hi ~ L08_hi + T2_lo = D_lo ~ L13_lo + T2_hi = D_hi ~ L13_hi + T3_lo = D_lo ~ L18_lo + T3_hi = D_hi ~ L18_hi + T4_lo = D_lo ~ L23_lo + T4_hi = D_hi ~ L23_hi + L03_lo = T2_lo>>21 ~ T2_hi<<11 + L03_hi = T2_hi>>21 ~ T2_lo<<11 + L08_lo = T4_lo>>3 ~ T4_hi<<29 + L08_hi = T4_hi>>3 ~ T4_lo<<29 + L13_lo = T1_lo<<6 ~ T1_hi>>26 + L13_hi = T1_hi<<6 ~ T1_lo>>26 + L18_lo = T3_lo<<15 ~ T3_hi>>17 + L18_hi = T3_hi<<15 ~ T3_lo>>17 + L23_lo = T0_lo>>2 ~ T0_hi<<30 + L23_hi = T0_hi>>2 ~ T0_lo<<30 + D_lo = C3_lo ~ C5_lo<<1 ~ C5_hi>>31 + D_hi = C3_hi ~ C5_hi<<1 ~ C5_lo>>31 + T0_lo = D_lo ~ L04_lo + T0_hi = D_hi ~ L04_hi + T1_lo = D_lo ~ L09_lo + T1_hi = D_hi ~ L09_hi + T2_lo = D_lo ~ L14_lo + T2_hi = D_hi ~ L14_hi + T3_lo = D_lo ~ L19_lo + T3_hi = D_hi ~ L19_hi + T4_lo = D_lo ~ L24_lo + T4_hi = D_hi ~ L24_hi + L04_lo = T3_lo<<21 ~ T3_hi>>11 + L04_hi = T3_hi<<21 ~ T3_lo>>11 + L09_lo = T0_lo<<28 ~ T0_hi>>4 + L09_hi = T0_hi<<28 ~ T0_lo>>4 + L14_lo = T2_lo<<25 ~ T2_hi>>7 + L14_hi = T2_hi<<25 ~ T2_lo>>7 + L19_lo = T4_lo>>8 ~ T4_hi<<24 + L19_hi = T4_hi>>8 ~ T4_lo<<24 + L24_lo = T1_lo>>9 ~ T1_hi<<23 + L24_hi = T1_hi>>9 ~ T1_lo<<23 + D_lo = C4_lo ~ C1_lo<<1 ~ C1_hi>>31 + D_hi = C4_hi ~ C1_hi<<1 ~ C1_lo>>31 + T0_lo = D_lo ~ L05_lo + T0_hi = D_hi ~ L05_hi + T1_lo = D_lo ~ L10_lo + T1_hi = D_hi ~ L10_hi + T2_lo = D_lo ~ L15_lo + T2_hi = D_hi ~ L15_hi + T3_lo = D_lo ~ L20_lo + T3_hi = D_hi ~ L20_hi + T4_lo = D_lo ~ L25_lo + T4_hi = D_hi ~ L25_hi + L05_lo = T4_lo<<14 ~ T4_hi>>18 + L05_hi = T4_hi<<14 ~ T4_lo>>18 + L10_lo = T1_lo<<20 ~ T1_hi>>12 + L10_hi = T1_hi<<20 ~ T1_lo>>12 + L15_lo = T3_lo<<8 ~ T3_hi>>24 + L15_hi = T3_hi<<8 ~ T3_lo>>24 + L20_lo = T0_lo<<27 ~ T0_hi>>5 + L20_hi = T0_hi<<27 ~ T0_lo>>5 + L25_lo = T2_lo>>25 ~ T2_hi<<7 + L25_hi = T2_hi>>25 ~ T2_lo<<7 + D_lo = C5_lo ~ C2_lo<<1 ~ C2_hi>>31 + D_hi = C5_hi ~ C2_hi<<1 ~ C2_lo>>31 + T1_lo = D_lo ~ L06_lo + T1_hi = D_hi ~ L06_hi + T2_lo = D_lo ~ L11_lo + T2_hi = D_hi ~ L11_hi + T3_lo = D_lo ~ L16_lo + T3_hi = D_hi ~ L16_hi + T4_lo = D_lo ~ L21_lo + T4_hi = D_hi ~ L21_hi + L06_lo = T2_lo<<3 ~ T2_hi>>29 + L06_hi = T2_hi<<3 ~ T2_lo>>29 + L11_lo = T4_lo<<18 ~ T4_hi>>14 + L11_hi = T4_hi<<18 ~ T4_lo>>14 + L16_lo = T1_lo>>28 ~ T1_hi<<4 + L16_hi = T1_hi>>28 ~ T1_lo<<4 + L21_lo = T3_lo>>23 ~ T3_hi<<9 + L21_hi = T3_hi>>23 ~ T3_lo<<9 + L01_lo = D_lo ~ L01_lo + L01_hi = D_hi ~ L01_hi + L01_lo, L02_lo, L03_lo, L04_lo, L05_lo = L01_lo ~ ~L02_lo & L03_lo, L02_lo ~ ~L03_lo & L04_lo, L03_lo ~ ~L04_lo & L05_lo, L04_lo ~ ~L05_lo & L01_lo, L05_lo ~ ~L01_lo & L02_lo + L01_hi, L02_hi, L03_hi, L04_hi, L05_hi = L01_hi ~ ~L02_hi & L03_hi, L02_hi ~ ~L03_hi & L04_hi, L03_hi ~ ~L04_hi & L05_hi, L04_hi ~ ~L05_hi & L01_hi, L05_hi ~ ~L01_hi & L02_hi + L06_lo, L07_lo, L08_lo, L09_lo, L10_lo = L09_lo ~ ~L10_lo & L06_lo, L10_lo ~ ~L06_lo & L07_lo, L06_lo ~ ~L07_lo & L08_lo, L07_lo ~ ~L08_lo & L09_lo, L08_lo ~ ~L09_lo & L10_lo + L06_hi, L07_hi, L08_hi, L09_hi, L10_hi = L09_hi ~ ~L10_hi & L06_hi, L10_hi ~ ~L06_hi & L07_hi, L06_hi ~ ~L07_hi & L08_hi, L07_hi ~ ~L08_hi & L09_hi, L08_hi ~ ~L09_hi & L10_hi + L11_lo, L12_lo, L13_lo, L14_lo, L15_lo = L12_lo ~ ~L13_lo & L14_lo, L13_lo ~ ~L14_lo & L15_lo, L14_lo ~ ~L15_lo & L11_lo, L15_lo ~ ~L11_lo & L12_lo, L11_lo ~ ~L12_lo & L13_lo + L11_hi, L12_hi, L13_hi, L14_hi, L15_hi = L12_hi ~ ~L13_hi & L14_hi, L13_hi ~ ~L14_hi & L15_hi, L14_hi ~ ~L15_hi & L11_hi, L15_hi ~ ~L11_hi & L12_hi, L11_hi ~ ~L12_hi & L13_hi + L16_lo, L17_lo, L18_lo, L19_lo, L20_lo = L20_lo ~ ~L16_lo & L17_lo, L16_lo ~ ~L17_lo & L18_lo, L17_lo ~ ~L18_lo & L19_lo, L18_lo ~ ~L19_lo & L20_lo, L19_lo ~ ~L20_lo & L16_lo + L16_hi, L17_hi, L18_hi, L19_hi, L20_hi = L20_hi ~ ~L16_hi & L17_hi, L16_hi ~ ~L17_hi & L18_hi, L17_hi ~ ~L18_hi & L19_hi, L18_hi ~ ~L19_hi & L20_hi, L19_hi ~ ~L20_hi & L16_hi + L21_lo, L22_lo, L23_lo, L24_lo, L25_lo = L23_lo ~ ~L24_lo & L25_lo, L24_lo ~ ~L25_lo & L21_lo, L25_lo ~ ~L21_lo & L22_lo, L21_lo ~ ~L22_lo & L23_lo, L22_lo ~ ~L23_lo & L24_lo + L21_hi, L22_hi, L23_hi, L24_hi, L25_hi = L23_hi ~ ~L24_hi & L25_hi, L24_hi ~ ~L25_hi & L21_hi, L25_hi ~ ~L21_hi & L22_hi, L21_hi ~ ~L22_hi & L23_hi, L22_hi ~ ~L23_hi & L24_hi + L01_lo = L01_lo ~ RC_lo[round_idx] + L01_hi = L01_hi ~ RC_hi[round_idx] + end + lanes_lo[1] = L01_lo; lanes_hi[1] = L01_hi + lanes_lo[2] = L02_lo; lanes_hi[2] = L02_hi + lanes_lo[3] = L03_lo; lanes_hi[3] = L03_hi + lanes_lo[4] = L04_lo; lanes_hi[4] = L04_hi + lanes_lo[5] = L05_lo; lanes_hi[5] = L05_hi + lanes_lo[6] = L06_lo; lanes_hi[6] = L06_hi + lanes_lo[7] = L07_lo; lanes_hi[7] = L07_hi + lanes_lo[8] = L08_lo; lanes_hi[8] = L08_hi + lanes_lo[9] = L09_lo; lanes_hi[9] = L09_hi + lanes_lo[10] = L10_lo; lanes_hi[10] = L10_hi + lanes_lo[11] = L11_lo; lanes_hi[11] = L11_hi + lanes_lo[12] = L12_lo; lanes_hi[12] = L12_hi + lanes_lo[13] = L13_lo; lanes_hi[13] = L13_hi + lanes_lo[14] = L14_lo; lanes_hi[14] = L14_hi + lanes_lo[15] = L15_lo; lanes_hi[15] = L15_hi + lanes_lo[16] = L16_lo; lanes_hi[16] = L16_hi + lanes_lo[17] = L17_lo; lanes_hi[17] = L17_hi + lanes_lo[18] = L18_lo; lanes_hi[18] = L18_hi + lanes_lo[19] = L19_lo; lanes_hi[19] = L19_hi + lanes_lo[20] = L20_lo; lanes_hi[20] = L20_hi + lanes_lo[21] = L21_lo; lanes_hi[21] = L21_hi + lanes_lo[22] = L22_lo; lanes_hi[22] = L22_hi + lanes_lo[23] = L23_lo; lanes_hi[23] = L23_hi + lanes_lo[24] = L24_lo; lanes_hi[24] = L24_hi + lanes_lo[25] = L25_lo; lanes_hi[25] = L25_hi + end + end + + local function blake2s_feed_64(H, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs + 1, offs + size, 64 do + if str then + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack("> 16 | vC << 16 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = v4 >> 12 | v4 << 20 + v0 = v0 + v4 + W[row[2]] + vC = vC ~ v0 + vC = vC >> 8 | vC << 24 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = v4 >> 7 | v4 << 25 + v1 = v1 + v5 + W[row[3]] + vD = vD ~ v1 + vD = vD >> 16 | vD << 16 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = v5 >> 12 | v5 << 20 + v1 = v1 + v5 + W[row[4]] + vD = vD ~ v1 + vD = vD >> 8 | vD << 24 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = v5 >> 7 | v5 << 25 + v2 = v2 + v6 + W[row[5]] + vE = vE ~ v2 + vE = vE >> 16 | vE << 16 + vA = vA + vE + v6 = v6 ~ vA + v6 = v6 >> 12 | v6 << 20 + v2 = v2 + v6 + W[row[6]] + vE = vE ~ v2 + vE = vE >> 8 | vE << 24 + vA = vA + vE + v6 = v6 ~ vA + v6 = v6 >> 7 | v6 << 25 + v3 = v3 + v7 + W[row[7]] + vF = vF ~ v3 + vF = vF >> 16 | vF << 16 + vB = vB + vF + v7 = v7 ~ vB + v7 = v7 >> 12 | v7 << 20 + v3 = v3 + v7 + W[row[8]] + vF = vF ~ v3 + vF = vF >> 8 | vF << 24 + vB = vB + vF + v7 = v7 ~ vB + v7 = v7 >> 7 | v7 << 25 + v0 = v0 + v5 + W[row[9]] + vF = vF ~ v0 + vF = vF >> 16 | vF << 16 + vA = vA + vF + v5 = v5 ~ vA + v5 = v5 >> 12 | v5 << 20 + v0 = v0 + v5 + W[row[10]] + vF = vF ~ v0 + vF = vF >> 8 | vF << 24 + vA = vA + vF + v5 = v5 ~ vA + v5 = v5 >> 7 | v5 << 25 + v1 = v1 + v6 + W[row[11]] + vC = vC ~ v1 + vC = vC >> 16 | vC << 16 + vB = vB + vC + v6 = v6 ~ vB + v6 = v6 >> 12 | v6 << 20 + v1 = v1 + v6 + W[row[12]] + vC = vC ~ v1 + vC = vC >> 8 | vC << 24 + vB = vB + vC + v6 = v6 ~ vB + v6 = v6 >> 7 | v6 << 25 + v2 = v2 + v7 + W[row[13]] + vD = vD ~ v2 + vD = vD >> 16 | vD << 16 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = v7 >> 12 | v7 << 20 + v2 = v2 + v7 + W[row[14]] + vD = vD ~ v2 + vD = vD >> 8 | vD << 24 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = v7 >> 7 | v7 << 25 + v3 = v3 + v4 + W[row[15]] + vE = vE ~ v3 + vE = vE >> 16 | vE << 16 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = v4 >> 12 | v4 << 20 + v3 = v3 + v4 + W[row[16]] + vE = vE ~ v3 + vE = vE >> 8 | vE << 24 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = v4 >> 7 | v4 << 25 + end + h1 = h1 ~ v0 ~ v8 + h2 = h2 ~ v1 ~ v9 + h3 = h3 ~ v2 ~ vA + h4 = h4 ~ v3 ~ vB + h5 = h5 ~ v4 ~ vC + h6 = h6 ~ v5 ~ vD + h7 = h7 ~ v6 ~ vE + h8 = h8 ~ v7 ~ vF + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + return bytes_compressed + end + + local function blake2b_feed_128(H_lo, H_hi, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 128 + local W = common_W + local h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] + local h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] + for pos = offs + 1, offs + size, 128 do + if str then + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16], + W[17], W[18], W[19], W[20], W[21], W[22], W[23], W[24], W[25], W[26], W[27], W[28], W[29], W[30], W[31], W[32] = + string_unpack("> 24 | v4_hi << 8, v4_hi >> 24 | v4_lo << 8 + k = row[2] * 2 + v0_lo = v0_lo % 2^32 + v4_lo % 2^32 + W[k-1] % 2^32 + v0_hi = v0_hi + v4_hi + floor(v0_lo / 2^32) + W[k] + v0_lo = 0|((v0_lo + 2^31) % 2^32 - 2^31) + vC_lo, vC_hi = vC_lo ~ v0_lo, vC_hi ~ v0_hi + vC_lo, vC_hi = vC_lo >> 16 | vC_hi << 16, vC_hi >> 16 | vC_lo << 16 + v8_lo = v8_lo % 2^32 + vC_lo % 2^32 + v8_hi = v8_hi + vC_hi + floor(v8_lo / 2^32) + v8_lo = 0|((v8_lo + 2^31) % 2^32 - 2^31) + v4_lo, v4_hi = v4_lo ~ v8_lo, v4_hi ~ v8_hi + v4_lo, v4_hi = v4_lo << 1 | v4_hi >> 31, v4_hi << 1 | v4_lo >> 31 + k = row[3] * 2 + v1_lo = v1_lo % 2^32 + v5_lo % 2^32 + W[k-1] % 2^32 + v1_hi = v1_hi + v5_hi + floor(v1_lo / 2^32) + W[k] + v1_lo = 0|((v1_lo + 2^31) % 2^32 - 2^31) + vD_lo, vD_hi = vD_hi ~ v1_hi, vD_lo ~ v1_lo + v9_lo = v9_lo % 2^32 + vD_lo % 2^32 + v9_hi = v9_hi + vD_hi + floor(v9_lo / 2^32) + v9_lo = 0|((v9_lo + 2^31) % 2^32 - 2^31) + v5_lo, v5_hi = v5_lo ~ v9_lo, v5_hi ~ v9_hi + v5_lo, v5_hi = v5_lo >> 24 | v5_hi << 8, v5_hi >> 24 | v5_lo << 8 + k = row[4] * 2 + v1_lo = v1_lo % 2^32 + v5_lo % 2^32 + W[k-1] % 2^32 + v1_hi = v1_hi + v5_hi + floor(v1_lo / 2^32) + W[k] + v1_lo = 0|((v1_lo + 2^31) % 2^32 - 2^31) + vD_lo, vD_hi = vD_lo ~ v1_lo, vD_hi ~ v1_hi + vD_lo, vD_hi = vD_lo >> 16 | vD_hi << 16, vD_hi >> 16 | vD_lo << 16 + v9_lo = v9_lo % 2^32 + vD_lo % 2^32 + v9_hi = v9_hi + vD_hi + floor(v9_lo / 2^32) + v9_lo = 0|((v9_lo + 2^31) % 2^32 - 2^31) + v5_lo, v5_hi = v5_lo ~ v9_lo, v5_hi ~ v9_hi + v5_lo, v5_hi = v5_lo << 1 | v5_hi >> 31, v5_hi << 1 | v5_lo >> 31 + k = row[5] * 2 + v2_lo = v2_lo % 2^32 + v6_lo % 2^32 + W[k-1] % 2^32 + v2_hi = v2_hi + v6_hi + floor(v2_lo / 2^32) + W[k] + v2_lo = 0|((v2_lo + 2^31) % 2^32 - 2^31) + vE_lo, vE_hi = vE_hi ~ v2_hi, vE_lo ~ v2_lo + vA_lo = vA_lo % 2^32 + vE_lo % 2^32 + vA_hi = vA_hi + vE_hi + floor(vA_lo / 2^32) + vA_lo = 0|((vA_lo + 2^31) % 2^32 - 2^31) + v6_lo, v6_hi = v6_lo ~ vA_lo, v6_hi ~ vA_hi + v6_lo, v6_hi = v6_lo >> 24 | v6_hi << 8, v6_hi >> 24 | v6_lo << 8 + k = row[6] * 2 + v2_lo = v2_lo % 2^32 + v6_lo % 2^32 + W[k-1] % 2^32 + v2_hi = v2_hi + v6_hi + floor(v2_lo / 2^32) + W[k] + v2_lo = 0|((v2_lo + 2^31) % 2^32 - 2^31) + vE_lo, vE_hi = vE_lo ~ v2_lo, vE_hi ~ v2_hi + vE_lo, vE_hi = vE_lo >> 16 | vE_hi << 16, vE_hi >> 16 | vE_lo << 16 + vA_lo = vA_lo % 2^32 + vE_lo % 2^32 + vA_hi = vA_hi + vE_hi + floor(vA_lo / 2^32) + vA_lo = 0|((vA_lo + 2^31) % 2^32 - 2^31) + v6_lo, v6_hi = v6_lo ~ vA_lo, v6_hi ~ vA_hi + v6_lo, v6_hi = v6_lo << 1 | v6_hi >> 31, v6_hi << 1 | v6_lo >> 31 + k = row[7] * 2 + v3_lo = v3_lo % 2^32 + v7_lo % 2^32 + W[k-1] % 2^32 + v3_hi = v3_hi + v7_hi + floor(v3_lo / 2^32) + W[k] + v3_lo = 0|((v3_lo + 2^31) % 2^32 - 2^31) + vF_lo, vF_hi = vF_hi ~ v3_hi, vF_lo ~ v3_lo + vB_lo = vB_lo % 2^32 + vF_lo % 2^32 + vB_hi = vB_hi + vF_hi + floor(vB_lo / 2^32) + vB_lo = 0|((vB_lo + 2^31) % 2^32 - 2^31) + v7_lo, v7_hi = v7_lo ~ vB_lo, v7_hi ~ vB_hi + v7_lo, v7_hi = v7_lo >> 24 | v7_hi << 8, v7_hi >> 24 | v7_lo << 8 + k = row[8] * 2 + v3_lo = v3_lo % 2^32 + v7_lo % 2^32 + W[k-1] % 2^32 + v3_hi = v3_hi + v7_hi + floor(v3_lo / 2^32) + W[k] + v3_lo = 0|((v3_lo + 2^31) % 2^32 - 2^31) + vF_lo, vF_hi = vF_lo ~ v3_lo, vF_hi ~ v3_hi + vF_lo, vF_hi = vF_lo >> 16 | vF_hi << 16, vF_hi >> 16 | vF_lo << 16 + vB_lo = vB_lo % 2^32 + vF_lo % 2^32 + vB_hi = vB_hi + vF_hi + floor(vB_lo / 2^32) + vB_lo = 0|((vB_lo + 2^31) % 2^32 - 2^31) + v7_lo, v7_hi = v7_lo ~ vB_lo, v7_hi ~ vB_hi + v7_lo, v7_hi = v7_lo << 1 | v7_hi >> 31, v7_hi << 1 | v7_lo >> 31 + k = row[9] * 2 + v0_lo = v0_lo % 2^32 + v5_lo % 2^32 + W[k-1] % 2^32 + v0_hi = v0_hi + v5_hi + floor(v0_lo / 2^32) + W[k] + v0_lo = 0|((v0_lo + 2^31) % 2^32 - 2^31) + vF_lo, vF_hi = vF_hi ~ v0_hi, vF_lo ~ v0_lo + vA_lo = vA_lo % 2^32 + vF_lo % 2^32 + vA_hi = vA_hi + vF_hi + floor(vA_lo / 2^32) + vA_lo = 0|((vA_lo + 2^31) % 2^32 - 2^31) + v5_lo, v5_hi = v5_lo ~ vA_lo, v5_hi ~ vA_hi + v5_lo, v5_hi = v5_lo >> 24 | v5_hi << 8, v5_hi >> 24 | v5_lo << 8 + k = row[10] * 2 + v0_lo = v0_lo % 2^32 + v5_lo % 2^32 + W[k-1] % 2^32 + v0_hi = v0_hi + v5_hi + floor(v0_lo / 2^32) + W[k] + v0_lo = 0|((v0_lo + 2^31) % 2^32 - 2^31) + vF_lo, vF_hi = vF_lo ~ v0_lo, vF_hi ~ v0_hi + vF_lo, vF_hi = vF_lo >> 16 | vF_hi << 16, vF_hi >> 16 | vF_lo << 16 + vA_lo = vA_lo % 2^32 + vF_lo % 2^32 + vA_hi = vA_hi + vF_hi + floor(vA_lo / 2^32) + vA_lo = 0|((vA_lo + 2^31) % 2^32 - 2^31) + v5_lo, v5_hi = v5_lo ~ vA_lo, v5_hi ~ vA_hi + v5_lo, v5_hi = v5_lo << 1 | v5_hi >> 31, v5_hi << 1 | v5_lo >> 31 + k = row[11] * 2 + v1_lo = v1_lo % 2^32 + v6_lo % 2^32 + W[k-1] % 2^32 + v1_hi = v1_hi + v6_hi + floor(v1_lo / 2^32) + W[k] + v1_lo = 0|((v1_lo + 2^31) % 2^32 - 2^31) + vC_lo, vC_hi = vC_hi ~ v1_hi, vC_lo ~ v1_lo + vB_lo = vB_lo % 2^32 + vC_lo % 2^32 + vB_hi = vB_hi + vC_hi + floor(vB_lo / 2^32) + vB_lo = 0|((vB_lo + 2^31) % 2^32 - 2^31) + v6_lo, v6_hi = v6_lo ~ vB_lo, v6_hi ~ vB_hi + v6_lo, v6_hi = v6_lo >> 24 | v6_hi << 8, v6_hi >> 24 | v6_lo << 8 + k = row[12] * 2 + v1_lo = v1_lo % 2^32 + v6_lo % 2^32 + W[k-1] % 2^32 + v1_hi = v1_hi + v6_hi + floor(v1_lo / 2^32) + W[k] + v1_lo = 0|((v1_lo + 2^31) % 2^32 - 2^31) + vC_lo, vC_hi = vC_lo ~ v1_lo, vC_hi ~ v1_hi + vC_lo, vC_hi = vC_lo >> 16 | vC_hi << 16, vC_hi >> 16 | vC_lo << 16 + vB_lo = vB_lo % 2^32 + vC_lo % 2^32 + vB_hi = vB_hi + vC_hi + floor(vB_lo / 2^32) + vB_lo = 0|((vB_lo + 2^31) % 2^32 - 2^31) + v6_lo, v6_hi = v6_lo ~ vB_lo, v6_hi ~ vB_hi + v6_lo, v6_hi = v6_lo << 1 | v6_hi >> 31, v6_hi << 1 | v6_lo >> 31 + k = row[13] * 2 + v2_lo = v2_lo % 2^32 + v7_lo % 2^32 + W[k-1] % 2^32 + v2_hi = v2_hi + v7_hi + floor(v2_lo / 2^32) + W[k] + v2_lo = 0|((v2_lo + 2^31) % 2^32 - 2^31) + vD_lo, vD_hi = vD_hi ~ v2_hi, vD_lo ~ v2_lo + v8_lo = v8_lo % 2^32 + vD_lo % 2^32 + v8_hi = v8_hi + vD_hi + floor(v8_lo / 2^32) + v8_lo = 0|((v8_lo + 2^31) % 2^32 - 2^31) + v7_lo, v7_hi = v7_lo ~ v8_lo, v7_hi ~ v8_hi + v7_lo, v7_hi = v7_lo >> 24 | v7_hi << 8, v7_hi >> 24 | v7_lo << 8 + k = row[14] * 2 + v2_lo = v2_lo % 2^32 + v7_lo % 2^32 + W[k-1] % 2^32 + v2_hi = v2_hi + v7_hi + floor(v2_lo / 2^32) + W[k] + v2_lo = 0|((v2_lo + 2^31) % 2^32 - 2^31) + vD_lo, vD_hi = vD_lo ~ v2_lo, vD_hi ~ v2_hi + vD_lo, vD_hi = vD_lo >> 16 | vD_hi << 16, vD_hi >> 16 | vD_lo << 16 + v8_lo = v8_lo % 2^32 + vD_lo % 2^32 + v8_hi = v8_hi + vD_hi + floor(v8_lo / 2^32) + v8_lo = 0|((v8_lo + 2^31) % 2^32 - 2^31) + v7_lo, v7_hi = v7_lo ~ v8_lo, v7_hi ~ v8_hi + v7_lo, v7_hi = v7_lo << 1 | v7_hi >> 31, v7_hi << 1 | v7_lo >> 31 + k = row[15] * 2 + v3_lo = v3_lo % 2^32 + v4_lo % 2^32 + W[k-1] % 2^32 + v3_hi = v3_hi + v4_hi + floor(v3_lo / 2^32) + W[k] + v3_lo = 0|((v3_lo + 2^31) % 2^32 - 2^31) + vE_lo, vE_hi = vE_hi ~ v3_hi, vE_lo ~ v3_lo + v9_lo = v9_lo % 2^32 + vE_lo % 2^32 + v9_hi = v9_hi + vE_hi + floor(v9_lo / 2^32) + v9_lo = 0|((v9_lo + 2^31) % 2^32 - 2^31) + v4_lo, v4_hi = v4_lo ~ v9_lo, v4_hi ~ v9_hi + v4_lo, v4_hi = v4_lo >> 24 | v4_hi << 8, v4_hi >> 24 | v4_lo << 8 + k = row[16] * 2 + v3_lo = v3_lo % 2^32 + v4_lo % 2^32 + W[k-1] % 2^32 + v3_hi = v3_hi + v4_hi + floor(v3_lo / 2^32) + W[k] + v3_lo = 0|((v3_lo + 2^31) % 2^32 - 2^31) + vE_lo, vE_hi = vE_lo ~ v3_lo, vE_hi ~ v3_hi + vE_lo, vE_hi = vE_lo >> 16 | vE_hi << 16, vE_hi >> 16 | vE_lo << 16 + v9_lo = v9_lo % 2^32 + vE_lo % 2^32 + v9_hi = v9_hi + vE_hi + floor(v9_lo / 2^32) + v9_lo = 0|((v9_lo + 2^31) % 2^32 - 2^31) + v4_lo, v4_hi = v4_lo ~ v9_lo, v4_hi ~ v9_hi + v4_lo, v4_hi = v4_lo << 1 | v4_hi >> 31, v4_hi << 1 | v4_lo >> 31 + end + h1_lo = h1_lo ~ v0_lo ~ v8_lo + h2_lo = h2_lo ~ v1_lo ~ v9_lo + h3_lo = h3_lo ~ v2_lo ~ vA_lo + h4_lo = h4_lo ~ v3_lo ~ vB_lo + h5_lo = h5_lo ~ v4_lo ~ vC_lo + h6_lo = h6_lo ~ v5_lo ~ vD_lo + h7_lo = h7_lo ~ v6_lo ~ vE_lo + h8_lo = h8_lo ~ v7_lo ~ vF_lo + h1_hi = h1_hi ~ v0_hi ~ v8_hi + h2_hi = h2_hi ~ v1_hi ~ v9_hi + h3_hi = h3_hi ~ v2_hi ~ vA_hi + h4_hi = h4_hi ~ v3_hi ~ vB_hi + h5_hi = h5_hi ~ v4_hi ~ vC_hi + h6_hi = h6_hi ~ v5_hi ~ vD_hi + h7_hi = h7_hi ~ v6_hi ~ vE_hi + h8_hi = h8_hi ~ v7_hi ~ vF_hi + end + H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + return bytes_compressed + end + + local function blake3_feed_64(str, offs, size, flags, chunk_index, H_in, H_out, wide_output, block_length) + -- offs >= 0, size >= 0, size is multiple of 64 + block_length = block_length or 64 + local W = common_W + local h1, h2, h3, h4, h5, h6, h7, h8 = H_in[1], H_in[2], H_in[3], H_in[4], H_in[5], H_in[6], H_in[7], H_in[8] + H_out = H_out or H_in + for pos = offs + 1, offs + size, 64 do + if str then + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack("> 16 | vC << 16 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = v4 >> 12 | v4 << 20 + v0 = v0 + v4 + W[perm_blake3[j + 14]] + vC = vC ~ v0 + vC = vC >> 8 | vC << 24 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = v4 >> 7 | v4 << 25 + v1 = v1 + v5 + W[perm_blake3[j + 1]] + vD = vD ~ v1 + vD = vD >> 16 | vD << 16 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = v5 >> 12 | v5 << 20 + v1 = v1 + v5 + W[perm_blake3[j + 2]] + vD = vD ~ v1 + vD = vD >> 8 | vD << 24 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = v5 >> 7 | v5 << 25 + v2 = v2 + v6 + W[perm_blake3[j + 16]] + vE = vE ~ v2 + vE = vE >> 16 | vE << 16 + vA = vA + vE + v6 = v6 ~ vA + v6 = v6 >> 12 | v6 << 20 + v2 = v2 + v6 + W[perm_blake3[j + 7]] + vE = vE ~ v2 + vE = vE >> 8 | vE << 24 + vA = vA + vE + v6 = v6 ~ vA + v6 = v6 >> 7 | v6 << 25 + v3 = v3 + v7 + W[perm_blake3[j + 15]] + vF = vF ~ v3 + vF = vF >> 16 | vF << 16 + vB = vB + vF + v7 = v7 ~ vB + v7 = v7 >> 12 | v7 << 20 + v3 = v3 + v7 + W[perm_blake3[j + 17]] + vF = vF ~ v3 + vF = vF >> 8 | vF << 24 + vB = vB + vF + v7 = v7 ~ vB + v7 = v7 >> 7 | v7 << 25 + v0 = v0 + v5 + W[perm_blake3[j + 21]] + vF = vF ~ v0 + vF = vF >> 16 | vF << 16 + vA = vA + vF + v5 = v5 ~ vA + v5 = v5 >> 12 | v5 << 20 + v0 = v0 + v5 + W[perm_blake3[j + 5]] + vF = vF ~ v0 + vF = vF >> 8 | vF << 24 + vA = vA + vF + v5 = v5 ~ vA + v5 = v5 >> 7 | v5 << 25 + v1 = v1 + v6 + W[perm_blake3[j + 3]] + vC = vC ~ v1 + vC = vC >> 16 | vC << 16 + vB = vB + vC + v6 = v6 ~ vB + v6 = v6 >> 12 | v6 << 20 + v1 = v1 + v6 + W[perm_blake3[j + 6]] + vC = vC ~ v1 + vC = vC >> 8 | vC << 24 + vB = vB + vC + v6 = v6 ~ vB + v6 = v6 >> 7 | v6 << 25 + v2 = v2 + v7 + W[perm_blake3[j + 4]] + vD = vD ~ v2 + vD = vD >> 16 | vD << 16 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = v7 >> 12 | v7 << 20 + v2 = v2 + v7 + W[perm_blake3[j + 18]] + vD = vD ~ v2 + vD = vD >> 8 | vD << 24 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = v7 >> 7 | v7 << 25 + v3 = v3 + v4 + W[perm_blake3[j + 19]] + vE = vE ~ v3 + vE = vE >> 16 | vE << 16 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = v4 >> 12 | v4 << 20 + v3 = v3 + v4 + W[perm_blake3[j + 20]] + vE = vE ~ v3 + vE = vE >> 8 | vE << 24 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = v4 >> 7 | v4 << 25 + end + if wide_output then + H_out[ 9] = h1 ~ v8 + H_out[10] = h2 ~ v9 + H_out[11] = h3 ~ vA + H_out[12] = h4 ~ vB + H_out[13] = h5 ~ vC + H_out[14] = h6 ~ vD + H_out[15] = h7 ~ vE + H_out[16] = h8 ~ vF + end + h1 = v0 ~ v8 + h2 = v1 ~ v9 + h3 = v2 ~ vA + h4 = v3 ~ vB + h5 = v4 ~ vC + h6 = v5 ~ vD + h7 = v6 ~ vE + h8 = v7 ~ vF + end + H_out[1], H_out[2], H_out[3], H_out[4], H_out[5], H_out[6], H_out[7], H_out[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + + return XORA5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64, keccak_feed, blake2s_feed_64, blake2b_feed_128, blake3_feed_64 + ]=](md5_next_shift, md5_K, sha2_K_lo, sha2_K_hi, build_keccak_format, sha3_RC_lo, sha3_RC_hi, sigma, common_W, sha2_H_lo, sha2_H_hi, perm_blake3) + +end + +XOR = XOR or XORA5 + +if branch == "LIB32" or branch == "EMUL" then + + + -- implementation for Lua 5.1/5.2 (with or without bitwise library available) + + function sha256_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K = common_W, sha2_K_hi + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs, offs + size - 1, 64 do + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((a * 256 + b) * 256 + c) * 256 + d + end + for j = 17, 64 do + local a, b = W[j-15], W[j-2] + local a7, a18, b17, b19 = a / 2^7, a / 2^18, b / 2^17, b / 2^19 + W[j] = (XOR(a7 % 1 * (2^32 - 1) + a7, a18 % 1 * (2^32 - 1) + a18, (a - a % 2^3) / 2^3) + W[j-16] + W[j-7] + + XOR(b17 % 1 * (2^32 - 1) + b17, b19 % 1 * (2^32 - 1) + b19, (b - b % 2^10) / 2^10)) % 2^32 + end + local a, b, c, d, e, f, g, h = h1, h2, h3, h4, h5, h6, h7, h8 + for j = 1, 64 do + e = e % 2^32 + local e6, e11, e7 = e / 2^6, e / 2^11, e * 2^7 + local e7_lo = e7 % 2^32 + local z = AND(e, f) + AND(-1-e, g) + h + K[j] + W[j] + + XOR(e6 % 1 * (2^32 - 1) + e6, e11 % 1 * (2^32 - 1) + e11, e7_lo + (e7 - e7_lo) / 2^32) + h = g + g = f + f = e + e = z + d + d = c + c = b + b = a % 2^32 + local b2, b13, b10 = b / 2^2, b / 2^13, b * 2^10 + local b10_lo = b10 % 2^32 + a = z + AND(d, c) + AND(b, XOR(d, c)) + + XOR(b2 % 1 * (2^32 - 1) + b2, b13 % 1 * (2^32 - 1) + b13, b10_lo + (b10 - b10_lo) / 2^32) + end + h1, h2, h3, h4 = (a + h1) % 2^32, (b + h2) % 2^32, (c + h3) % 2^32, (d + h4) % 2^32 + h5, h6, h7, h8 = (e + h5) % 2^32, (f + h6) % 2^32, (g + h7) % 2^32, (h + h8) % 2^32 + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + + + function sha512_feed_128(H_lo, H_hi, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 128 + -- W1_hi, W1_lo, W2_hi, W2_lo, ... Wk_hi = W[2*k-1], Wk_lo = W[2*k] + local W, K_lo, K_hi = common_W, sha2_K_lo, sha2_K_hi + local h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] + local h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] + for pos = offs, offs + size - 1, 128 do + for j = 1, 16*2 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((a * 256 + b) * 256 + c) * 256 + d + end + for jj = 17*2, 80*2, 2 do + local a_hi, a_lo, b_hi, b_lo = W[jj-31], W[jj-30], W[jj-5], W[jj-4] + local b_hi_6, b_hi_19, b_hi_29, b_lo_19, b_lo_29, a_hi_1, a_hi_7, a_hi_8, a_lo_1, a_lo_8 = + b_hi % 2^6, b_hi % 2^19, b_hi % 2^29, b_lo % 2^19, b_lo % 2^29, a_hi % 2^1, a_hi % 2^7, a_hi % 2^8, a_lo % 2^1, a_lo % 2^8 + local tmp1 = XOR((a_lo - a_lo_1) / 2^1 + a_hi_1 * 2^31, (a_lo - a_lo_8) / 2^8 + a_hi_8 * 2^24, (a_lo - a_lo % 2^7) / 2^7 + a_hi_7 * 2^25) % 2^32 + + XOR((b_lo - b_lo_19) / 2^19 + b_hi_19 * 2^13, b_lo_29 * 2^3 + (b_hi - b_hi_29) / 2^29, (b_lo - b_lo % 2^6) / 2^6 + b_hi_6 * 2^26) % 2^32 + + W[jj-14] + W[jj-32] + local tmp2 = tmp1 % 2^32 + W[jj-1] = (XOR((a_hi - a_hi_1) / 2^1 + a_lo_1 * 2^31, (a_hi - a_hi_8) / 2^8 + a_lo_8 * 2^24, (a_hi - a_hi_7) / 2^7) + + XOR((b_hi - b_hi_19) / 2^19 + b_lo_19 * 2^13, b_hi_29 * 2^3 + (b_lo - b_lo_29) / 2^29, (b_hi - b_hi_6) / 2^6) + + W[jj-15] + W[jj-33] + (tmp1 - tmp2) / 2^32) % 2^32 + W[jj] = tmp2 + end + local a_lo, b_lo, c_lo, d_lo, e_lo, f_lo, g_lo, h_lo = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + local a_hi, b_hi, c_hi, d_hi, e_hi, f_hi, g_hi, h_hi = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + for j = 1, 80 do + local jj = 2*j + local e_lo_9, e_lo_14, e_lo_18, e_hi_9, e_hi_14, e_hi_18 = e_lo % 2^9, e_lo % 2^14, e_lo % 2^18, e_hi % 2^9, e_hi % 2^14, e_hi % 2^18 + local tmp1 = (AND(e_lo, f_lo) + AND(-1-e_lo, g_lo)) % 2^32 + h_lo + K_lo[j] + W[jj] + + XOR((e_lo - e_lo_14) / 2^14 + e_hi_14 * 2^18, (e_lo - e_lo_18) / 2^18 + e_hi_18 * 2^14, e_lo_9 * 2^23 + (e_hi - e_hi_9) / 2^9) % 2^32 + local z_lo = tmp1 % 2^32 + local z_hi = AND(e_hi, f_hi) + AND(-1-e_hi, g_hi) + h_hi + K_hi[j] + W[jj-1] + (tmp1 - z_lo) / 2^32 + + XOR((e_hi - e_hi_14) / 2^14 + e_lo_14 * 2^18, (e_hi - e_hi_18) / 2^18 + e_lo_18 * 2^14, e_hi_9 * 2^23 + (e_lo - e_lo_9) / 2^9) + h_lo = g_lo; h_hi = g_hi + g_lo = f_lo; g_hi = f_hi + f_lo = e_lo; f_hi = e_hi + tmp1 = z_lo + d_lo + e_lo = tmp1 % 2^32 + e_hi = (z_hi + d_hi + (tmp1 - e_lo) / 2^32) % 2^32 + d_lo = c_lo; d_hi = c_hi + c_lo = b_lo; c_hi = b_hi + b_lo = a_lo; b_hi = a_hi + local b_lo_2, b_lo_7, b_lo_28, b_hi_2, b_hi_7, b_hi_28 = b_lo % 2^2, b_lo % 2^7, b_lo % 2^28, b_hi % 2^2, b_hi % 2^7, b_hi % 2^28 + tmp1 = z_lo + (AND(d_lo, c_lo) + AND(b_lo, XOR(d_lo, c_lo))) % 2^32 + + XOR((b_lo - b_lo_28) / 2^28 + b_hi_28 * 2^4, b_lo_2 * 2^30 + (b_hi - b_hi_2) / 2^2, b_lo_7 * 2^25 + (b_hi - b_hi_7) / 2^7) % 2^32 + a_lo = tmp1 % 2^32 + a_hi = (z_hi + AND(d_hi, c_hi) + AND(b_hi, XOR(d_hi, c_hi)) + (tmp1 - a_lo) / 2^32 + + XOR((b_hi - b_hi_28) / 2^28 + b_lo_28 * 2^4, b_hi_2 * 2^30 + (b_lo - b_lo_2) / 2^2, b_hi_7 * 2^25 + (b_lo - b_lo_7) / 2^7)) % 2^32 + end + a_lo = h1_lo + a_lo + h1_lo = a_lo % 2^32 + h1_hi = (h1_hi + a_hi + (a_lo - h1_lo) / 2^32) % 2^32 + a_lo = h2_lo + b_lo + h2_lo = a_lo % 2^32 + h2_hi = (h2_hi + b_hi + (a_lo - h2_lo) / 2^32) % 2^32 + a_lo = h3_lo + c_lo + h3_lo = a_lo % 2^32 + h3_hi = (h3_hi + c_hi + (a_lo - h3_lo) / 2^32) % 2^32 + a_lo = h4_lo + d_lo + h4_lo = a_lo % 2^32 + h4_hi = (h4_hi + d_hi + (a_lo - h4_lo) / 2^32) % 2^32 + a_lo = h5_lo + e_lo + h5_lo = a_lo % 2^32 + h5_hi = (h5_hi + e_hi + (a_lo - h5_lo) / 2^32) % 2^32 + a_lo = h6_lo + f_lo + h6_lo = a_lo % 2^32 + h6_hi = (h6_hi + f_hi + (a_lo - h6_lo) / 2^32) % 2^32 + a_lo = h7_lo + g_lo + h7_lo = a_lo % 2^32 + h7_hi = (h7_hi + g_hi + (a_lo - h7_lo) / 2^32) % 2^32 + a_lo = h8_lo + h_lo + h8_lo = a_lo % 2^32 + h8_hi = (h8_hi + h_hi + (a_lo - h8_lo) / 2^32) % 2^32 + end + H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + end + + + if branch == "LIB32" then + + function md5_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K, md5_next_shift = common_W, md5_K, md5_next_shift + local h1, h2, h3, h4 = H[1], H[2], H[3], H[4] + for pos = offs, offs + size - 1, 64 do + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((d * 256 + c) * 256 + b) * 256 + a + end + local a, b, c, d = h1, h2, h3, h4 + local s = 25 + for j = 1, 16 do + local F = ROR(AND(b, c) + AND(-1-b, d) + a + K[j] + W[j], s) + b + s = md5_next_shift[s] + a = d + d = c + c = b + b = F + end + s = 27 + for j = 17, 32 do + local F = ROR(AND(d, b) + AND(-1-d, c) + a + K[j] + W[(5*j-4) % 16 + 1], s) + b + s = md5_next_shift[s] + a = d + d = c + c = b + b = F + end + s = 28 + for j = 33, 48 do + local F = ROR(XOR(XOR(b, c), d) + a + K[j] + W[(3*j+2) % 16 + 1], s) + b + s = md5_next_shift[s] + a = d + d = c + c = b + b = F + end + s = 26 + for j = 49, 64 do + local F = ROR(XOR(c, OR(b, -1-d)) + a + K[j] + W[(j*7-7) % 16 + 1], s) + b + s = md5_next_shift[s] + a = d + d = c + c = b + b = F + end + h1 = (a + h1) % 2^32 + h2 = (b + h2) % 2^32 + h3 = (c + h3) % 2^32 + h4 = (d + h4) % 2^32 + end + H[1], H[2], H[3], H[4] = h1, h2, h3, h4 + end + + elseif branch == "EMUL" then + + function md5_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K, md5_next_shift = common_W, md5_K, md5_next_shift + local h1, h2, h3, h4 = H[1], H[2], H[3], H[4] + for pos = offs, offs + size - 1, 64 do + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((d * 256 + c) * 256 + b) * 256 + a + end + local a, b, c, d = h1, h2, h3, h4 + local s = 25 + for j = 1, 16 do + local z = (AND(b, c) + AND(-1-b, d) + a + K[j] + W[j]) % 2^32 / 2^s + local y = z % 1 + s = md5_next_shift[s] + a = d + d = c + c = b + b = y * 2^32 + (z - y) + b + end + s = 27 + for j = 17, 32 do + local z = (AND(d, b) + AND(-1-d, c) + a + K[j] + W[(5*j-4) % 16 + 1]) % 2^32 / 2^s + local y = z % 1 + s = md5_next_shift[s] + a = d + d = c + c = b + b = y * 2^32 + (z - y) + b + end + s = 28 + for j = 33, 48 do + local z = (XOR(XOR(b, c), d) + a + K[j] + W[(3*j+2) % 16 + 1]) % 2^32 / 2^s + local y = z % 1 + s = md5_next_shift[s] + a = d + d = c + c = b + b = y * 2^32 + (z - y) + b + end + s = 26 + for j = 49, 64 do + local z = (XOR(c, OR(b, -1-d)) + a + K[j] + W[(j*7-7) % 16 + 1]) % 2^32 / 2^s + local y = z % 1 + s = md5_next_shift[s] + a = d + d = c + c = b + b = y * 2^32 + (z - y) + b + end + h1 = (a + h1) % 2^32 + h2 = (b + h2) % 2^32 + h3 = (c + h3) % 2^32 + h4 = (d + h4) % 2^32 + end + H[1], H[2], H[3], H[4] = h1, h2, h3, h4 + end + + end + + + function sha1_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W + local h1, h2, h3, h4, h5 = H[1], H[2], H[3], H[4], H[5] + for pos = offs, offs + size - 1, 64 do + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((a * 256 + b) * 256 + c) * 256 + d + end + for j = 17, 80 do + local a = XOR(W[j-3], W[j-8], W[j-14], W[j-16]) % 2^32 * 2 + local b = a % 2^32 + W[j] = b + (a - b) / 2^32 + end + local a, b, c, d, e = h1, h2, h3, h4, h5 + for j = 1, 20 do + local a5 = a * 2^5 + local z = a5 % 2^32 + z = z + (a5 - z) / 2^32 + AND(b, c) + AND(-1-b, d) + 0x5A827999 + W[j] + e -- constant = floor(2^30 * sqrt(2)) + e = d + d = c + c = b / 2^2 + c = c % 1 * (2^32 - 1) + c + b = a + a = z % 2^32 + end + for j = 21, 40 do + local a5 = a * 2^5 + local z = a5 % 2^32 + z = z + (a5 - z) / 2^32 + XOR(b, c, d) + 0x6ED9EBA1 + W[j] + e -- 2^30 * sqrt(3) + e = d + d = c + c = b / 2^2 + c = c % 1 * (2^32 - 1) + c + b = a + a = z % 2^32 + end + for j = 41, 60 do + local a5 = a * 2^5 + local z = a5 % 2^32 + z = z + (a5 - z) / 2^32 + AND(d, c) + AND(b, XOR(d, c)) + 0x8F1BBCDC + W[j] + e -- 2^30 * sqrt(5) + e = d + d = c + c = b / 2^2 + c = c % 1 * (2^32 - 1) + c + b = a + a = z % 2^32 + end + for j = 61, 80 do + local a5 = a * 2^5 + local z = a5 % 2^32 + z = z + (a5 - z) / 2^32 + XOR(b, c, d) + 0xCA62C1D6 + W[j] + e -- 2^30 * sqrt(10) + e = d + d = c + c = b / 2^2 + c = c % 1 * (2^32 - 1) + c + b = a + a = z % 2^32 + end + h1 = (a + h1) % 2^32 + h2 = (b + h2) % 2^32 + h3 = (c + h3) % 2^32 + h4 = (d + h4) % 2^32 + h5 = (e + h5) % 2^32 + end + H[1], H[2], H[3], H[4], H[5] = h1, h2, h3, h4, h5 + end + + + function keccak_feed(lanes_lo, lanes_hi, str, offs, size, block_size_in_bytes) + -- This is an example of a Lua function having 79 local variables :-) + -- offs >= 0, size >= 0, size is multiple of block_size_in_bytes, block_size_in_bytes is positive multiple of 8 + local RC_lo, RC_hi = sha3_RC_lo, sha3_RC_hi + local qwords_qty = block_size_in_bytes / 8 + for pos = offs, offs + size - 1, block_size_in_bytes do + for j = 1, qwords_qty do + local a, b, c, d = byte(str, pos + 1, pos + 4) + lanes_lo[j] = XOR(lanes_lo[j], ((d * 256 + c) * 256 + b) * 256 + a) + pos = pos + 8 + a, b, c, d = byte(str, pos - 3, pos) + lanes_hi[j] = XOR(lanes_hi[j], ((d * 256 + c) * 256 + b) * 256 + a) + end + local L01_lo, L01_hi, L02_lo, L02_hi, L03_lo, L03_hi, L04_lo, L04_hi, L05_lo, L05_hi, L06_lo, L06_hi, L07_lo, L07_hi, L08_lo, L08_hi, + L09_lo, L09_hi, L10_lo, L10_hi, L11_lo, L11_hi, L12_lo, L12_hi, L13_lo, L13_hi, L14_lo, L14_hi, L15_lo, L15_hi, L16_lo, L16_hi, + L17_lo, L17_hi, L18_lo, L18_hi, L19_lo, L19_hi, L20_lo, L20_hi, L21_lo, L21_hi, L22_lo, L22_hi, L23_lo, L23_hi, L24_lo, L24_hi, L25_lo, L25_hi = + lanes_lo[1], lanes_hi[1], lanes_lo[2], lanes_hi[2], lanes_lo[3], lanes_hi[3], lanes_lo[4], lanes_hi[4], lanes_lo[5], lanes_hi[5], + lanes_lo[6], lanes_hi[6], lanes_lo[7], lanes_hi[7], lanes_lo[8], lanes_hi[8], lanes_lo[9], lanes_hi[9], lanes_lo[10], lanes_hi[10], + lanes_lo[11], lanes_hi[11], lanes_lo[12], lanes_hi[12], lanes_lo[13], lanes_hi[13], lanes_lo[14], lanes_hi[14], lanes_lo[15], lanes_hi[15], + lanes_lo[16], lanes_hi[16], lanes_lo[17], lanes_hi[17], lanes_lo[18], lanes_hi[18], lanes_lo[19], lanes_hi[19], lanes_lo[20], lanes_hi[20], + lanes_lo[21], lanes_hi[21], lanes_lo[22], lanes_hi[22], lanes_lo[23], lanes_hi[23], lanes_lo[24], lanes_hi[24], lanes_lo[25], lanes_hi[25] + for round_idx = 1, 24 do + local C1_lo = XOR(L01_lo, L06_lo, L11_lo, L16_lo, L21_lo) + local C1_hi = XOR(L01_hi, L06_hi, L11_hi, L16_hi, L21_hi) + local C2_lo = XOR(L02_lo, L07_lo, L12_lo, L17_lo, L22_lo) + local C2_hi = XOR(L02_hi, L07_hi, L12_hi, L17_hi, L22_hi) + local C3_lo = XOR(L03_lo, L08_lo, L13_lo, L18_lo, L23_lo) + local C3_hi = XOR(L03_hi, L08_hi, L13_hi, L18_hi, L23_hi) + local C4_lo = XOR(L04_lo, L09_lo, L14_lo, L19_lo, L24_lo) + local C4_hi = XOR(L04_hi, L09_hi, L14_hi, L19_hi, L24_hi) + local C5_lo = XOR(L05_lo, L10_lo, L15_lo, L20_lo, L25_lo) + local C5_hi = XOR(L05_hi, L10_hi, L15_hi, L20_hi, L25_hi) + local D_lo = XOR(C1_lo, C3_lo * 2 + (C3_hi % 2^32 - C3_hi % 2^31) / 2^31) + local D_hi = XOR(C1_hi, C3_hi * 2 + (C3_lo % 2^32 - C3_lo % 2^31) / 2^31) + local T0_lo = XOR(D_lo, L02_lo) + local T0_hi = XOR(D_hi, L02_hi) + local T1_lo = XOR(D_lo, L07_lo) + local T1_hi = XOR(D_hi, L07_hi) + local T2_lo = XOR(D_lo, L12_lo) + local T2_hi = XOR(D_hi, L12_hi) + local T3_lo = XOR(D_lo, L17_lo) + local T3_hi = XOR(D_hi, L17_hi) + local T4_lo = XOR(D_lo, L22_lo) + local T4_hi = XOR(D_hi, L22_hi) + L02_lo = (T1_lo % 2^32 - T1_lo % 2^20) / 2^20 + T1_hi * 2^12 + L02_hi = (T1_hi % 2^32 - T1_hi % 2^20) / 2^20 + T1_lo * 2^12 + L07_lo = (T3_lo % 2^32 - T3_lo % 2^19) / 2^19 + T3_hi * 2^13 + L07_hi = (T3_hi % 2^32 - T3_hi % 2^19) / 2^19 + T3_lo * 2^13 + L12_lo = T0_lo * 2 + (T0_hi % 2^32 - T0_hi % 2^31) / 2^31 + L12_hi = T0_hi * 2 + (T0_lo % 2^32 - T0_lo % 2^31) / 2^31 + L17_lo = T2_lo * 2^10 + (T2_hi % 2^32 - T2_hi % 2^22) / 2^22 + L17_hi = T2_hi * 2^10 + (T2_lo % 2^32 - T2_lo % 2^22) / 2^22 + L22_lo = T4_lo * 2^2 + (T4_hi % 2^32 - T4_hi % 2^30) / 2^30 + L22_hi = T4_hi * 2^2 + (T4_lo % 2^32 - T4_lo % 2^30) / 2^30 + D_lo = XOR(C2_lo, C4_lo * 2 + (C4_hi % 2^32 - C4_hi % 2^31) / 2^31) + D_hi = XOR(C2_hi, C4_hi * 2 + (C4_lo % 2^32 - C4_lo % 2^31) / 2^31) + T0_lo = XOR(D_lo, L03_lo) + T0_hi = XOR(D_hi, L03_hi) + T1_lo = XOR(D_lo, L08_lo) + T1_hi = XOR(D_hi, L08_hi) + T2_lo = XOR(D_lo, L13_lo) + T2_hi = XOR(D_hi, L13_hi) + T3_lo = XOR(D_lo, L18_lo) + T3_hi = XOR(D_hi, L18_hi) + T4_lo = XOR(D_lo, L23_lo) + T4_hi = XOR(D_hi, L23_hi) + L03_lo = (T2_lo % 2^32 - T2_lo % 2^21) / 2^21 + T2_hi * 2^11 + L03_hi = (T2_hi % 2^32 - T2_hi % 2^21) / 2^21 + T2_lo * 2^11 + L08_lo = (T4_lo % 2^32 - T4_lo % 2^3) / 2^3 + T4_hi * 2^29 % 2^32 + L08_hi = (T4_hi % 2^32 - T4_hi % 2^3) / 2^3 + T4_lo * 2^29 % 2^32 + L13_lo = T1_lo * 2^6 + (T1_hi % 2^32 - T1_hi % 2^26) / 2^26 + L13_hi = T1_hi * 2^6 + (T1_lo % 2^32 - T1_lo % 2^26) / 2^26 + L18_lo = T3_lo * 2^15 + (T3_hi % 2^32 - T3_hi % 2^17) / 2^17 + L18_hi = T3_hi * 2^15 + (T3_lo % 2^32 - T3_lo % 2^17) / 2^17 + L23_lo = (T0_lo % 2^32 - T0_lo % 2^2) / 2^2 + T0_hi * 2^30 % 2^32 + L23_hi = (T0_hi % 2^32 - T0_hi % 2^2) / 2^2 + T0_lo * 2^30 % 2^32 + D_lo = XOR(C3_lo, C5_lo * 2 + (C5_hi % 2^32 - C5_hi % 2^31) / 2^31) + D_hi = XOR(C3_hi, C5_hi * 2 + (C5_lo % 2^32 - C5_lo % 2^31) / 2^31) + T0_lo = XOR(D_lo, L04_lo) + T0_hi = XOR(D_hi, L04_hi) + T1_lo = XOR(D_lo, L09_lo) + T1_hi = XOR(D_hi, L09_hi) + T2_lo = XOR(D_lo, L14_lo) + T2_hi = XOR(D_hi, L14_hi) + T3_lo = XOR(D_lo, L19_lo) + T3_hi = XOR(D_hi, L19_hi) + T4_lo = XOR(D_lo, L24_lo) + T4_hi = XOR(D_hi, L24_hi) + L04_lo = T3_lo * 2^21 % 2^32 + (T3_hi % 2^32 - T3_hi % 2^11) / 2^11 + L04_hi = T3_hi * 2^21 % 2^32 + (T3_lo % 2^32 - T3_lo % 2^11) / 2^11 + L09_lo = T0_lo * 2^28 % 2^32 + (T0_hi % 2^32 - T0_hi % 2^4) / 2^4 + L09_hi = T0_hi * 2^28 % 2^32 + (T0_lo % 2^32 - T0_lo % 2^4) / 2^4 + L14_lo = T2_lo * 2^25 % 2^32 + (T2_hi % 2^32 - T2_hi % 2^7) / 2^7 + L14_hi = T2_hi * 2^25 % 2^32 + (T2_lo % 2^32 - T2_lo % 2^7) / 2^7 + L19_lo = (T4_lo % 2^32 - T4_lo % 2^8) / 2^8 + T4_hi * 2^24 % 2^32 + L19_hi = (T4_hi % 2^32 - T4_hi % 2^8) / 2^8 + T4_lo * 2^24 % 2^32 + L24_lo = (T1_lo % 2^32 - T1_lo % 2^9) / 2^9 + T1_hi * 2^23 % 2^32 + L24_hi = (T1_hi % 2^32 - T1_hi % 2^9) / 2^9 + T1_lo * 2^23 % 2^32 + D_lo = XOR(C4_lo, C1_lo * 2 + (C1_hi % 2^32 - C1_hi % 2^31) / 2^31) + D_hi = XOR(C4_hi, C1_hi * 2 + (C1_lo % 2^32 - C1_lo % 2^31) / 2^31) + T0_lo = XOR(D_lo, L05_lo) + T0_hi = XOR(D_hi, L05_hi) + T1_lo = XOR(D_lo, L10_lo) + T1_hi = XOR(D_hi, L10_hi) + T2_lo = XOR(D_lo, L15_lo) + T2_hi = XOR(D_hi, L15_hi) + T3_lo = XOR(D_lo, L20_lo) + T3_hi = XOR(D_hi, L20_hi) + T4_lo = XOR(D_lo, L25_lo) + T4_hi = XOR(D_hi, L25_hi) + L05_lo = T4_lo * 2^14 + (T4_hi % 2^32 - T4_hi % 2^18) / 2^18 + L05_hi = T4_hi * 2^14 + (T4_lo % 2^32 - T4_lo % 2^18) / 2^18 + L10_lo = T1_lo * 2^20 % 2^32 + (T1_hi % 2^32 - T1_hi % 2^12) / 2^12 + L10_hi = T1_hi * 2^20 % 2^32 + (T1_lo % 2^32 - T1_lo % 2^12) / 2^12 + L15_lo = T3_lo * 2^8 + (T3_hi % 2^32 - T3_hi % 2^24) / 2^24 + L15_hi = T3_hi * 2^8 + (T3_lo % 2^32 - T3_lo % 2^24) / 2^24 + L20_lo = T0_lo * 2^27 % 2^32 + (T0_hi % 2^32 - T0_hi % 2^5) / 2^5 + L20_hi = T0_hi * 2^27 % 2^32 + (T0_lo % 2^32 - T0_lo % 2^5) / 2^5 + L25_lo = (T2_lo % 2^32 - T2_lo % 2^25) / 2^25 + T2_hi * 2^7 + L25_hi = (T2_hi % 2^32 - T2_hi % 2^25) / 2^25 + T2_lo * 2^7 + D_lo = XOR(C5_lo, C2_lo * 2 + (C2_hi % 2^32 - C2_hi % 2^31) / 2^31) + D_hi = XOR(C5_hi, C2_hi * 2 + (C2_lo % 2^32 - C2_lo % 2^31) / 2^31) + T1_lo = XOR(D_lo, L06_lo) + T1_hi = XOR(D_hi, L06_hi) + T2_lo = XOR(D_lo, L11_lo) + T2_hi = XOR(D_hi, L11_hi) + T3_lo = XOR(D_lo, L16_lo) + T3_hi = XOR(D_hi, L16_hi) + T4_lo = XOR(D_lo, L21_lo) + T4_hi = XOR(D_hi, L21_hi) + L06_lo = T2_lo * 2^3 + (T2_hi % 2^32 - T2_hi % 2^29) / 2^29 + L06_hi = T2_hi * 2^3 + (T2_lo % 2^32 - T2_lo % 2^29) / 2^29 + L11_lo = T4_lo * 2^18 + (T4_hi % 2^32 - T4_hi % 2^14) / 2^14 + L11_hi = T4_hi * 2^18 + (T4_lo % 2^32 - T4_lo % 2^14) / 2^14 + L16_lo = (T1_lo % 2^32 - T1_lo % 2^28) / 2^28 + T1_hi * 2^4 + L16_hi = (T1_hi % 2^32 - T1_hi % 2^28) / 2^28 + T1_lo * 2^4 + L21_lo = (T3_lo % 2^32 - T3_lo % 2^23) / 2^23 + T3_hi * 2^9 + L21_hi = (T3_hi % 2^32 - T3_hi % 2^23) / 2^23 + T3_lo * 2^9 + L01_lo = XOR(D_lo, L01_lo) + L01_hi = XOR(D_hi, L01_hi) + L01_lo, L02_lo, L03_lo, L04_lo, L05_lo = XOR(L01_lo, AND(-1-L02_lo, L03_lo)), XOR(L02_lo, AND(-1-L03_lo, L04_lo)), XOR(L03_lo, AND(-1-L04_lo, L05_lo)), XOR(L04_lo, AND(-1-L05_lo, L01_lo)), XOR(L05_lo, AND(-1-L01_lo, L02_lo)) + L01_hi, L02_hi, L03_hi, L04_hi, L05_hi = XOR(L01_hi, AND(-1-L02_hi, L03_hi)), XOR(L02_hi, AND(-1-L03_hi, L04_hi)), XOR(L03_hi, AND(-1-L04_hi, L05_hi)), XOR(L04_hi, AND(-1-L05_hi, L01_hi)), XOR(L05_hi, AND(-1-L01_hi, L02_hi)) + L06_lo, L07_lo, L08_lo, L09_lo, L10_lo = XOR(L09_lo, AND(-1-L10_lo, L06_lo)), XOR(L10_lo, AND(-1-L06_lo, L07_lo)), XOR(L06_lo, AND(-1-L07_lo, L08_lo)), XOR(L07_lo, AND(-1-L08_lo, L09_lo)), XOR(L08_lo, AND(-1-L09_lo, L10_lo)) + L06_hi, L07_hi, L08_hi, L09_hi, L10_hi = XOR(L09_hi, AND(-1-L10_hi, L06_hi)), XOR(L10_hi, AND(-1-L06_hi, L07_hi)), XOR(L06_hi, AND(-1-L07_hi, L08_hi)), XOR(L07_hi, AND(-1-L08_hi, L09_hi)), XOR(L08_hi, AND(-1-L09_hi, L10_hi)) + L11_lo, L12_lo, L13_lo, L14_lo, L15_lo = XOR(L12_lo, AND(-1-L13_lo, L14_lo)), XOR(L13_lo, AND(-1-L14_lo, L15_lo)), XOR(L14_lo, AND(-1-L15_lo, L11_lo)), XOR(L15_lo, AND(-1-L11_lo, L12_lo)), XOR(L11_lo, AND(-1-L12_lo, L13_lo)) + L11_hi, L12_hi, L13_hi, L14_hi, L15_hi = XOR(L12_hi, AND(-1-L13_hi, L14_hi)), XOR(L13_hi, AND(-1-L14_hi, L15_hi)), XOR(L14_hi, AND(-1-L15_hi, L11_hi)), XOR(L15_hi, AND(-1-L11_hi, L12_hi)), XOR(L11_hi, AND(-1-L12_hi, L13_hi)) + L16_lo, L17_lo, L18_lo, L19_lo, L20_lo = XOR(L20_lo, AND(-1-L16_lo, L17_lo)), XOR(L16_lo, AND(-1-L17_lo, L18_lo)), XOR(L17_lo, AND(-1-L18_lo, L19_lo)), XOR(L18_lo, AND(-1-L19_lo, L20_lo)), XOR(L19_lo, AND(-1-L20_lo, L16_lo)) + L16_hi, L17_hi, L18_hi, L19_hi, L20_hi = XOR(L20_hi, AND(-1-L16_hi, L17_hi)), XOR(L16_hi, AND(-1-L17_hi, L18_hi)), XOR(L17_hi, AND(-1-L18_hi, L19_hi)), XOR(L18_hi, AND(-1-L19_hi, L20_hi)), XOR(L19_hi, AND(-1-L20_hi, L16_hi)) + L21_lo, L22_lo, L23_lo, L24_lo, L25_lo = XOR(L23_lo, AND(-1-L24_lo, L25_lo)), XOR(L24_lo, AND(-1-L25_lo, L21_lo)), XOR(L25_lo, AND(-1-L21_lo, L22_lo)), XOR(L21_lo, AND(-1-L22_lo, L23_lo)), XOR(L22_lo, AND(-1-L23_lo, L24_lo)) + L21_hi, L22_hi, L23_hi, L24_hi, L25_hi = XOR(L23_hi, AND(-1-L24_hi, L25_hi)), XOR(L24_hi, AND(-1-L25_hi, L21_hi)), XOR(L25_hi, AND(-1-L21_hi, L22_hi)), XOR(L21_hi, AND(-1-L22_hi, L23_hi)), XOR(L22_hi, AND(-1-L23_hi, L24_hi)) + L01_lo = XOR(L01_lo, RC_lo[round_idx]) + L01_hi = L01_hi + RC_hi[round_idx] -- RC_hi[] is either 0 or 0x80000000, so we could use fast addition instead of slow XOR + end + lanes_lo[1] = L01_lo; lanes_hi[1] = L01_hi + lanes_lo[2] = L02_lo; lanes_hi[2] = L02_hi + lanes_lo[3] = L03_lo; lanes_hi[3] = L03_hi + lanes_lo[4] = L04_lo; lanes_hi[4] = L04_hi + lanes_lo[5] = L05_lo; lanes_hi[5] = L05_hi + lanes_lo[6] = L06_lo; lanes_hi[6] = L06_hi + lanes_lo[7] = L07_lo; lanes_hi[7] = L07_hi + lanes_lo[8] = L08_lo; lanes_hi[8] = L08_hi + lanes_lo[9] = L09_lo; lanes_hi[9] = L09_hi + lanes_lo[10] = L10_lo; lanes_hi[10] = L10_hi + lanes_lo[11] = L11_lo; lanes_hi[11] = L11_hi + lanes_lo[12] = L12_lo; lanes_hi[12] = L12_hi + lanes_lo[13] = L13_lo; lanes_hi[13] = L13_hi + lanes_lo[14] = L14_lo; lanes_hi[14] = L14_hi + lanes_lo[15] = L15_lo; lanes_hi[15] = L15_hi + lanes_lo[16] = L16_lo; lanes_hi[16] = L16_hi + lanes_lo[17] = L17_lo; lanes_hi[17] = L17_hi + lanes_lo[18] = L18_lo; lanes_hi[18] = L18_hi + lanes_lo[19] = L19_lo; lanes_hi[19] = L19_hi + lanes_lo[20] = L20_lo; lanes_hi[20] = L20_hi + lanes_lo[21] = L21_lo; lanes_hi[21] = L21_hi + lanes_lo[22] = L22_lo; lanes_hi[22] = L22_hi + lanes_lo[23] = L23_lo; lanes_hi[23] = L23_hi + lanes_lo[24] = L24_lo; lanes_hi[24] = L24_hi + lanes_lo[25] = L25_lo; lanes_hi[25] = L25_hi + end + end + + + function blake2s_feed_64(H, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs, offs + size - 1, 64 do + if str then + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((d * 256 + c) * 256 + b) * 256 + a + end + end + local v0, v1, v2, v3, v4, v5, v6, v7 = h1, h2, h3, h4, h5, h6, h7, h8 + local v8, v9, vA, vB, vC, vD, vE, vF = sha2_H_hi[1], sha2_H_hi[2], sha2_H_hi[3], sha2_H_hi[4], sha2_H_hi[5], sha2_H_hi[6], sha2_H_hi[7], sha2_H_hi[8] + bytes_compressed = bytes_compressed + (last_block_size or 64) + local t0 = bytes_compressed % 2^32 + local t1 = (bytes_compressed - t0) / 2^32 + vC = XOR(vC, t0) -- t0 = low_4_bytes(bytes_compressed) + vD = XOR(vD, t1) -- t1 = high_4_bytes(bytes_compressed) + if last_block_size then -- flag f0 + vE = -1 - vE + end + if is_last_node then -- flag f1 + vF = -1 - vF + end + for j = 1, 10 do + local row = sigma[j] + v0 = v0 + v4 + W[row[1]] + vC = XOR(vC, v0) % 2^32 / 2^16 + vC = vC % 1 * (2^32 - 1) + vC + v8 = v8 + vC + v4 = XOR(v4, v8) % 2^32 / 2^12 + v4 = v4 % 1 * (2^32 - 1) + v4 + v0 = v0 + v4 + W[row[2]] + vC = XOR(vC, v0) % 2^32 / 2^8 + vC = vC % 1 * (2^32 - 1) + vC + v8 = v8 + vC + v4 = XOR(v4, v8) % 2^32 / 2^7 + v4 = v4 % 1 * (2^32 - 1) + v4 + v1 = v1 + v5 + W[row[3]] + vD = XOR(vD, v1) % 2^32 / 2^16 + vD = vD % 1 * (2^32 - 1) + vD + v9 = v9 + vD + v5 = XOR(v5, v9) % 2^32 / 2^12 + v5 = v5 % 1 * (2^32 - 1) + v5 + v1 = v1 + v5 + W[row[4]] + vD = XOR(vD, v1) % 2^32 / 2^8 + vD = vD % 1 * (2^32 - 1) + vD + v9 = v9 + vD + v5 = XOR(v5, v9) % 2^32 / 2^7 + v5 = v5 % 1 * (2^32 - 1) + v5 + v2 = v2 + v6 + W[row[5]] + vE = XOR(vE, v2) % 2^32 / 2^16 + vE = vE % 1 * (2^32 - 1) + vE + vA = vA + vE + v6 = XOR(v6, vA) % 2^32 / 2^12 + v6 = v6 % 1 * (2^32 - 1) + v6 + v2 = v2 + v6 + W[row[6]] + vE = XOR(vE, v2) % 2^32 / 2^8 + vE = vE % 1 * (2^32 - 1) + vE + vA = vA + vE + v6 = XOR(v6, vA) % 2^32 / 2^7 + v6 = v6 % 1 * (2^32 - 1) + v6 + v3 = v3 + v7 + W[row[7]] + vF = XOR(vF, v3) % 2^32 / 2^16 + vF = vF % 1 * (2^32 - 1) + vF + vB = vB + vF + v7 = XOR(v7, vB) % 2^32 / 2^12 + v7 = v7 % 1 * (2^32 - 1) + v7 + v3 = v3 + v7 + W[row[8]] + vF = XOR(vF, v3) % 2^32 / 2^8 + vF = vF % 1 * (2^32 - 1) + vF + vB = vB + vF + v7 = XOR(v7, vB) % 2^32 / 2^7 + v7 = v7 % 1 * (2^32 - 1) + v7 + v0 = v0 + v5 + W[row[9]] + vF = XOR(vF, v0) % 2^32 / 2^16 + vF = vF % 1 * (2^32 - 1) + vF + vA = vA + vF + v5 = XOR(v5, vA) % 2^32 / 2^12 + v5 = v5 % 1 * (2^32 - 1) + v5 + v0 = v0 + v5 + W[row[10]] + vF = XOR(vF, v0) % 2^32 / 2^8 + vF = vF % 1 * (2^32 - 1) + vF + vA = vA + vF + v5 = XOR(v5, vA) % 2^32 / 2^7 + v5 = v5 % 1 * (2^32 - 1) + v5 + v1 = v1 + v6 + W[row[11]] + vC = XOR(vC, v1) % 2^32 / 2^16 + vC = vC % 1 * (2^32 - 1) + vC + vB = vB + vC + v6 = XOR(v6, vB) % 2^32 / 2^12 + v6 = v6 % 1 * (2^32 - 1) + v6 + v1 = v1 + v6 + W[row[12]] + vC = XOR(vC, v1) % 2^32 / 2^8 + vC = vC % 1 * (2^32 - 1) + vC + vB = vB + vC + v6 = XOR(v6, vB) % 2^32 / 2^7 + v6 = v6 % 1 * (2^32 - 1) + v6 + v2 = v2 + v7 + W[row[13]] + vD = XOR(vD, v2) % 2^32 / 2^16 + vD = vD % 1 * (2^32 - 1) + vD + v8 = v8 + vD + v7 = XOR(v7, v8) % 2^32 / 2^12 + v7 = v7 % 1 * (2^32 - 1) + v7 + v2 = v2 + v7 + W[row[14]] + vD = XOR(vD, v2) % 2^32 / 2^8 + vD = vD % 1 * (2^32 - 1) + vD + v8 = v8 + vD + v7 = XOR(v7, v8) % 2^32 / 2^7 + v7 = v7 % 1 * (2^32 - 1) + v7 + v3 = v3 + v4 + W[row[15]] + vE = XOR(vE, v3) % 2^32 / 2^16 + vE = vE % 1 * (2^32 - 1) + vE + v9 = v9 + vE + v4 = XOR(v4, v9) % 2^32 / 2^12 + v4 = v4 % 1 * (2^32 - 1) + v4 + v3 = v3 + v4 + W[row[16]] + vE = XOR(vE, v3) % 2^32 / 2^8 + vE = vE % 1 * (2^32 - 1) + vE + v9 = v9 + vE + v4 = XOR(v4, v9) % 2^32 / 2^7 + v4 = v4 % 1 * (2^32 - 1) + v4 + end + h1 = XOR(h1, v0, v8) + h2 = XOR(h2, v1, v9) + h3 = XOR(h3, v2, vA) + h4 = XOR(h4, v3, vB) + h5 = XOR(h5, v4, vC) + h6 = XOR(h6, v5, vD) + h7 = XOR(h7, v6, vE) + h8 = XOR(h8, v7, vF) + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + return bytes_compressed + end + + + function blake2b_feed_128(H_lo, H_hi, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 128 + local W = common_W + local h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] + local h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] + for pos = offs, offs + size - 1, 128 do + if str then + for j = 1, 32 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((d * 256 + c) * 256 + b) * 256 + a + end + end + local v0_lo, v1_lo, v2_lo, v3_lo, v4_lo, v5_lo, v6_lo, v7_lo = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + local v0_hi, v1_hi, v2_hi, v3_hi, v4_hi, v5_hi, v6_hi, v7_hi = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + local v8_lo, v9_lo, vA_lo, vB_lo, vC_lo, vD_lo, vE_lo, vF_lo = sha2_H_lo[1], sha2_H_lo[2], sha2_H_lo[3], sha2_H_lo[4], sha2_H_lo[5], sha2_H_lo[6], sha2_H_lo[7], sha2_H_lo[8] + local v8_hi, v9_hi, vA_hi, vB_hi, vC_hi, vD_hi, vE_hi, vF_hi = sha2_H_hi[1], sha2_H_hi[2], sha2_H_hi[3], sha2_H_hi[4], sha2_H_hi[5], sha2_H_hi[6], sha2_H_hi[7], sha2_H_hi[8] + bytes_compressed = bytes_compressed + (last_block_size or 128) + local t0_lo = bytes_compressed % 2^32 + local t0_hi = (bytes_compressed - t0_lo) / 2^32 + vC_lo = XOR(vC_lo, t0_lo) -- t0 = low_8_bytes(bytes_compressed) + vC_hi = XOR(vC_hi, t0_hi) + -- t1 = high_8_bytes(bytes_compressed) = 0, message length is always below 2^53 bytes + if last_block_size then -- flag f0 + vE_lo = -1 - vE_lo + vE_hi = -1 - vE_hi + end + if is_last_node then -- flag f1 + vF_lo = -1 - vF_lo + vF_hi = -1 - vF_hi + end + for j = 1, 12 do + local row = sigma[j] + local k = row[1] * 2 + local z = v0_lo % 2^32 + v4_lo % 2^32 + W[k-1] + v0_lo = z % 2^32 + v0_hi = v0_hi + v4_hi + (z - v0_lo) / 2^32 + W[k] + vC_lo, vC_hi = XOR(vC_hi, v0_hi), XOR(vC_lo, v0_lo) + z = v8_lo % 2^32 + vC_lo % 2^32 + v8_lo = z % 2^32 + v8_hi = v8_hi + vC_hi + (z - v8_lo) / 2^32 + v4_lo, v4_hi = XOR(v4_lo, v8_lo), XOR(v4_hi, v8_hi) + local z_lo, z_hi = v4_lo % 2^24, v4_hi % 2^24 + v4_lo, v4_hi = (v4_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v4_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[2] * 2 + z = v0_lo % 2^32 + v4_lo % 2^32 + W[k-1] + v0_lo = z % 2^32 + v0_hi = v0_hi + v4_hi + (z - v0_lo) / 2^32 + W[k] + vC_lo, vC_hi = XOR(vC_lo, v0_lo), XOR(vC_hi, v0_hi) + z_lo, z_hi = vC_lo % 2^16, vC_hi % 2^16 + vC_lo, vC_hi = (vC_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vC_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = v8_lo % 2^32 + vC_lo % 2^32 + v8_lo = z % 2^32 + v8_hi = v8_hi + vC_hi + (z - v8_lo) / 2^32 + v4_lo, v4_hi = XOR(v4_lo, v8_lo), XOR(v4_hi, v8_hi) + z_lo, z_hi = v4_lo % 2^31, v4_hi % 2^31 + v4_lo, v4_hi = z_lo * 2^1 + (v4_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v4_lo - z_lo) / 2^31 % 2^1 + k = row[3] * 2 + z = v1_lo % 2^32 + v5_lo % 2^32 + W[k-1] + v1_lo = z % 2^32 + v1_hi = v1_hi + v5_hi + (z - v1_lo) / 2^32 + W[k] + vD_lo, vD_hi = XOR(vD_hi, v1_hi), XOR(vD_lo, v1_lo) + z = v9_lo % 2^32 + vD_lo % 2^32 + v9_lo = z % 2^32 + v9_hi = v9_hi + vD_hi + (z - v9_lo) / 2^32 + v5_lo, v5_hi = XOR(v5_lo, v9_lo), XOR(v5_hi, v9_hi) + z_lo, z_hi = v5_lo % 2^24, v5_hi % 2^24 + v5_lo, v5_hi = (v5_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v5_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[4] * 2 + z = v1_lo % 2^32 + v5_lo % 2^32 + W[k-1] + v1_lo = z % 2^32 + v1_hi = v1_hi + v5_hi + (z - v1_lo) / 2^32 + W[k] + vD_lo, vD_hi = XOR(vD_lo, v1_lo), XOR(vD_hi, v1_hi) + z_lo, z_hi = vD_lo % 2^16, vD_hi % 2^16 + vD_lo, vD_hi = (vD_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vD_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = v9_lo % 2^32 + vD_lo % 2^32 + v9_lo = z % 2^32 + v9_hi = v9_hi + vD_hi + (z - v9_lo) / 2^32 + v5_lo, v5_hi = XOR(v5_lo, v9_lo), XOR(v5_hi, v9_hi) + z_lo, z_hi = v5_lo % 2^31, v5_hi % 2^31 + v5_lo, v5_hi = z_lo * 2^1 + (v5_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v5_lo - z_lo) / 2^31 % 2^1 + k = row[5] * 2 + z = v2_lo % 2^32 + v6_lo % 2^32 + W[k-1] + v2_lo = z % 2^32 + v2_hi = v2_hi + v6_hi + (z - v2_lo) / 2^32 + W[k] + vE_lo, vE_hi = XOR(vE_hi, v2_hi), XOR(vE_lo, v2_lo) + z = vA_lo % 2^32 + vE_lo % 2^32 + vA_lo = z % 2^32 + vA_hi = vA_hi + vE_hi + (z - vA_lo) / 2^32 + v6_lo, v6_hi = XOR(v6_lo, vA_lo), XOR(v6_hi, vA_hi) + z_lo, z_hi = v6_lo % 2^24, v6_hi % 2^24 + v6_lo, v6_hi = (v6_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v6_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[6] * 2 + z = v2_lo % 2^32 + v6_lo % 2^32 + W[k-1] + v2_lo = z % 2^32 + v2_hi = v2_hi + v6_hi + (z - v2_lo) / 2^32 + W[k] + vE_lo, vE_hi = XOR(vE_lo, v2_lo), XOR(vE_hi, v2_hi) + z_lo, z_hi = vE_lo % 2^16, vE_hi % 2^16 + vE_lo, vE_hi = (vE_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vE_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = vA_lo % 2^32 + vE_lo % 2^32 + vA_lo = z % 2^32 + vA_hi = vA_hi + vE_hi + (z - vA_lo) / 2^32 + v6_lo, v6_hi = XOR(v6_lo, vA_lo), XOR(v6_hi, vA_hi) + z_lo, z_hi = v6_lo % 2^31, v6_hi % 2^31 + v6_lo, v6_hi = z_lo * 2^1 + (v6_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v6_lo - z_lo) / 2^31 % 2^1 + k = row[7] * 2 + z = v3_lo % 2^32 + v7_lo % 2^32 + W[k-1] + v3_lo = z % 2^32 + v3_hi = v3_hi + v7_hi + (z - v3_lo) / 2^32 + W[k] + vF_lo, vF_hi = XOR(vF_hi, v3_hi), XOR(vF_lo, v3_lo) + z = vB_lo % 2^32 + vF_lo % 2^32 + vB_lo = z % 2^32 + vB_hi = vB_hi + vF_hi + (z - vB_lo) / 2^32 + v7_lo, v7_hi = XOR(v7_lo, vB_lo), XOR(v7_hi, vB_hi) + z_lo, z_hi = v7_lo % 2^24, v7_hi % 2^24 + v7_lo, v7_hi = (v7_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v7_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[8] * 2 + z = v3_lo % 2^32 + v7_lo % 2^32 + W[k-1] + v3_lo = z % 2^32 + v3_hi = v3_hi + v7_hi + (z - v3_lo) / 2^32 + W[k] + vF_lo, vF_hi = XOR(vF_lo, v3_lo), XOR(vF_hi, v3_hi) + z_lo, z_hi = vF_lo % 2^16, vF_hi % 2^16 + vF_lo, vF_hi = (vF_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vF_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = vB_lo % 2^32 + vF_lo % 2^32 + vB_lo = z % 2^32 + vB_hi = vB_hi + vF_hi + (z - vB_lo) / 2^32 + v7_lo, v7_hi = XOR(v7_lo, vB_lo), XOR(v7_hi, vB_hi) + z_lo, z_hi = v7_lo % 2^31, v7_hi % 2^31 + v7_lo, v7_hi = z_lo * 2^1 + (v7_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v7_lo - z_lo) / 2^31 % 2^1 + k = row[9] * 2 + z = v0_lo % 2^32 + v5_lo % 2^32 + W[k-1] + v0_lo = z % 2^32 + v0_hi = v0_hi + v5_hi + (z - v0_lo) / 2^32 + W[k] + vF_lo, vF_hi = XOR(vF_hi, v0_hi), XOR(vF_lo, v0_lo) + z = vA_lo % 2^32 + vF_lo % 2^32 + vA_lo = z % 2^32 + vA_hi = vA_hi + vF_hi + (z - vA_lo) / 2^32 + v5_lo, v5_hi = XOR(v5_lo, vA_lo), XOR(v5_hi, vA_hi) + z_lo, z_hi = v5_lo % 2^24, v5_hi % 2^24 + v5_lo, v5_hi = (v5_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v5_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[10] * 2 + z = v0_lo % 2^32 + v5_lo % 2^32 + W[k-1] + v0_lo = z % 2^32 + v0_hi = v0_hi + v5_hi + (z - v0_lo) / 2^32 + W[k] + vF_lo, vF_hi = XOR(vF_lo, v0_lo), XOR(vF_hi, v0_hi) + z_lo, z_hi = vF_lo % 2^16, vF_hi % 2^16 + vF_lo, vF_hi = (vF_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vF_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = vA_lo % 2^32 + vF_lo % 2^32 + vA_lo = z % 2^32 + vA_hi = vA_hi + vF_hi + (z - vA_lo) / 2^32 + v5_lo, v5_hi = XOR(v5_lo, vA_lo), XOR(v5_hi, vA_hi) + z_lo, z_hi = v5_lo % 2^31, v5_hi % 2^31 + v5_lo, v5_hi = z_lo * 2^1 + (v5_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v5_lo - z_lo) / 2^31 % 2^1 + k = row[11] * 2 + z = v1_lo % 2^32 + v6_lo % 2^32 + W[k-1] + v1_lo = z % 2^32 + v1_hi = v1_hi + v6_hi + (z - v1_lo) / 2^32 + W[k] + vC_lo, vC_hi = XOR(vC_hi, v1_hi), XOR(vC_lo, v1_lo) + z = vB_lo % 2^32 + vC_lo % 2^32 + vB_lo = z % 2^32 + vB_hi = vB_hi + vC_hi + (z - vB_lo) / 2^32 + v6_lo, v6_hi = XOR(v6_lo, vB_lo), XOR(v6_hi, vB_hi) + z_lo, z_hi = v6_lo % 2^24, v6_hi % 2^24 + v6_lo, v6_hi = (v6_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v6_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[12] * 2 + z = v1_lo % 2^32 + v6_lo % 2^32 + W[k-1] + v1_lo = z % 2^32 + v1_hi = v1_hi + v6_hi + (z - v1_lo) / 2^32 + W[k] + vC_lo, vC_hi = XOR(vC_lo, v1_lo), XOR(vC_hi, v1_hi) + z_lo, z_hi = vC_lo % 2^16, vC_hi % 2^16 + vC_lo, vC_hi = (vC_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vC_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = vB_lo % 2^32 + vC_lo % 2^32 + vB_lo = z % 2^32 + vB_hi = vB_hi + vC_hi + (z - vB_lo) / 2^32 + v6_lo, v6_hi = XOR(v6_lo, vB_lo), XOR(v6_hi, vB_hi) + z_lo, z_hi = v6_lo % 2^31, v6_hi % 2^31 + v6_lo, v6_hi = z_lo * 2^1 + (v6_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v6_lo - z_lo) / 2^31 % 2^1 + k = row[13] * 2 + z = v2_lo % 2^32 + v7_lo % 2^32 + W[k-1] + v2_lo = z % 2^32 + v2_hi = v2_hi + v7_hi + (z - v2_lo) / 2^32 + W[k] + vD_lo, vD_hi = XOR(vD_hi, v2_hi), XOR(vD_lo, v2_lo) + z = v8_lo % 2^32 + vD_lo % 2^32 + v8_lo = z % 2^32 + v8_hi = v8_hi + vD_hi + (z - v8_lo) / 2^32 + v7_lo, v7_hi = XOR(v7_lo, v8_lo), XOR(v7_hi, v8_hi) + z_lo, z_hi = v7_lo % 2^24, v7_hi % 2^24 + v7_lo, v7_hi = (v7_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v7_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[14] * 2 + z = v2_lo % 2^32 + v7_lo % 2^32 + W[k-1] + v2_lo = z % 2^32 + v2_hi = v2_hi + v7_hi + (z - v2_lo) / 2^32 + W[k] + vD_lo, vD_hi = XOR(vD_lo, v2_lo), XOR(vD_hi, v2_hi) + z_lo, z_hi = vD_lo % 2^16, vD_hi % 2^16 + vD_lo, vD_hi = (vD_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vD_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = v8_lo % 2^32 + vD_lo % 2^32 + v8_lo = z % 2^32 + v8_hi = v8_hi + vD_hi + (z - v8_lo) / 2^32 + v7_lo, v7_hi = XOR(v7_lo, v8_lo), XOR(v7_hi, v8_hi) + z_lo, z_hi = v7_lo % 2^31, v7_hi % 2^31 + v7_lo, v7_hi = z_lo * 2^1 + (v7_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v7_lo - z_lo) / 2^31 % 2^1 + k = row[15] * 2 + z = v3_lo % 2^32 + v4_lo % 2^32 + W[k-1] + v3_lo = z % 2^32 + v3_hi = v3_hi + v4_hi + (z - v3_lo) / 2^32 + W[k] + vE_lo, vE_hi = XOR(vE_hi, v3_hi), XOR(vE_lo, v3_lo) + z = v9_lo % 2^32 + vE_lo % 2^32 + v9_lo = z % 2^32 + v9_hi = v9_hi + vE_hi + (z - v9_lo) / 2^32 + v4_lo, v4_hi = XOR(v4_lo, v9_lo), XOR(v4_hi, v9_hi) + z_lo, z_hi = v4_lo % 2^24, v4_hi % 2^24 + v4_lo, v4_hi = (v4_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v4_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[16] * 2 + z = v3_lo % 2^32 + v4_lo % 2^32 + W[k-1] + v3_lo = z % 2^32 + v3_hi = v3_hi + v4_hi + (z - v3_lo) / 2^32 + W[k] + vE_lo, vE_hi = XOR(vE_lo, v3_lo), XOR(vE_hi, v3_hi) + z_lo, z_hi = vE_lo % 2^16, vE_hi % 2^16 + vE_lo, vE_hi = (vE_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vE_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = v9_lo % 2^32 + vE_lo % 2^32 + v9_lo = z % 2^32 + v9_hi = v9_hi + vE_hi + (z - v9_lo) / 2^32 + v4_lo, v4_hi = XOR(v4_lo, v9_lo), XOR(v4_hi, v9_hi) + z_lo, z_hi = v4_lo % 2^31, v4_hi % 2^31 + v4_lo, v4_hi = z_lo * 2^1 + (v4_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v4_lo - z_lo) / 2^31 % 2^1 + end + h1_lo = XOR(h1_lo, v0_lo, v8_lo) % 2^32 + h2_lo = XOR(h2_lo, v1_lo, v9_lo) % 2^32 + h3_lo = XOR(h3_lo, v2_lo, vA_lo) % 2^32 + h4_lo = XOR(h4_lo, v3_lo, vB_lo) % 2^32 + h5_lo = XOR(h5_lo, v4_lo, vC_lo) % 2^32 + h6_lo = XOR(h6_lo, v5_lo, vD_lo) % 2^32 + h7_lo = XOR(h7_lo, v6_lo, vE_lo) % 2^32 + h8_lo = XOR(h8_lo, v7_lo, vF_lo) % 2^32 + h1_hi = XOR(h1_hi, v0_hi, v8_hi) % 2^32 + h2_hi = XOR(h2_hi, v1_hi, v9_hi) % 2^32 + h3_hi = XOR(h3_hi, v2_hi, vA_hi) % 2^32 + h4_hi = XOR(h4_hi, v3_hi, vB_hi) % 2^32 + h5_hi = XOR(h5_hi, v4_hi, vC_hi) % 2^32 + h6_hi = XOR(h6_hi, v5_hi, vD_hi) % 2^32 + h7_hi = XOR(h7_hi, v6_hi, vE_hi) % 2^32 + h8_hi = XOR(h8_hi, v7_hi, vF_hi) % 2^32 + end + H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + return bytes_compressed + end + + + function blake3_feed_64(str, offs, size, flags, chunk_index, H_in, H_out, wide_output, block_length) + -- offs >= 0, size >= 0, size is multiple of 64 + block_length = block_length or 64 + local W = common_W + local h1, h2, h3, h4, h5, h6, h7, h8 = H_in[1], H_in[2], H_in[3], H_in[4], H_in[5], H_in[6], H_in[7], H_in[8] + H_out = H_out or H_in + for pos = offs, offs + size - 1, 64 do + if str then + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((d * 256 + c) * 256 + b) * 256 + a + end + end + local v0, v1, v2, v3, v4, v5, v6, v7 = h1, h2, h3, h4, h5, h6, h7, h8 + local v8, v9, vA, vB = sha2_H_hi[1], sha2_H_hi[2], sha2_H_hi[3], sha2_H_hi[4] + local vC = chunk_index % 2^32 -- t0 = low_4_bytes(chunk_index) + local vD = (chunk_index - vC) / 2^32 -- t1 = high_4_bytes(chunk_index) + local vE, vF = block_length, flags + for j = 1, 7 do + v0 = v0 + v4 + W[perm_blake3[j]] + vC = XOR(vC, v0) % 2^32 / 2^16 + vC = vC % 1 * (2^32 - 1) + vC + v8 = v8 + vC + v4 = XOR(v4, v8) % 2^32 / 2^12 + v4 = v4 % 1 * (2^32 - 1) + v4 + v0 = v0 + v4 + W[perm_blake3[j + 14]] + vC = XOR(vC, v0) % 2^32 / 2^8 + vC = vC % 1 * (2^32 - 1) + vC + v8 = v8 + vC + v4 = XOR(v4, v8) % 2^32 / 2^7 + v4 = v4 % 1 * (2^32 - 1) + v4 + v1 = v1 + v5 + W[perm_blake3[j + 1]] + vD = XOR(vD, v1) % 2^32 / 2^16 + vD = vD % 1 * (2^32 - 1) + vD + v9 = v9 + vD + v5 = XOR(v5, v9) % 2^32 / 2^12 + v5 = v5 % 1 * (2^32 - 1) + v5 + v1 = v1 + v5 + W[perm_blake3[j + 2]] + vD = XOR(vD, v1) % 2^32 / 2^8 + vD = vD % 1 * (2^32 - 1) + vD + v9 = v9 + vD + v5 = XOR(v5, v9) % 2^32 / 2^7 + v5 = v5 % 1 * (2^32 - 1) + v5 + v2 = v2 + v6 + W[perm_blake3[j + 16]] + vE = XOR(vE, v2) % 2^32 / 2^16 + vE = vE % 1 * (2^32 - 1) + vE + vA = vA + vE + v6 = XOR(v6, vA) % 2^32 / 2^12 + v6 = v6 % 1 * (2^32 - 1) + v6 + v2 = v2 + v6 + W[perm_blake3[j + 7]] + vE = XOR(vE, v2) % 2^32 / 2^8 + vE = vE % 1 * (2^32 - 1) + vE + vA = vA + vE + v6 = XOR(v6, vA) % 2^32 / 2^7 + v6 = v6 % 1 * (2^32 - 1) + v6 + v3 = v3 + v7 + W[perm_blake3[j + 15]] + vF = XOR(vF, v3) % 2^32 / 2^16 + vF = vF % 1 * (2^32 - 1) + vF + vB = vB + vF + v7 = XOR(v7, vB) % 2^32 / 2^12 + v7 = v7 % 1 * (2^32 - 1) + v7 + v3 = v3 + v7 + W[perm_blake3[j + 17]] + vF = XOR(vF, v3) % 2^32 / 2^8 + vF = vF % 1 * (2^32 - 1) + vF + vB = vB + vF + v7 = XOR(v7, vB) % 2^32 / 2^7 + v7 = v7 % 1 * (2^32 - 1) + v7 + v0 = v0 + v5 + W[perm_blake3[j + 21]] + vF = XOR(vF, v0) % 2^32 / 2^16 + vF = vF % 1 * (2^32 - 1) + vF + vA = vA + vF + v5 = XOR(v5, vA) % 2^32 / 2^12 + v5 = v5 % 1 * (2^32 - 1) + v5 + v0 = v0 + v5 + W[perm_blake3[j + 5]] + vF = XOR(vF, v0) % 2^32 / 2^8 + vF = vF % 1 * (2^32 - 1) + vF + vA = vA + vF + v5 = XOR(v5, vA) % 2^32 / 2^7 + v5 = v5 % 1 * (2^32 - 1) + v5 + v1 = v1 + v6 + W[perm_blake3[j + 3]] + vC = XOR(vC, v1) % 2^32 / 2^16 + vC = vC % 1 * (2^32 - 1) + vC + vB = vB + vC + v6 = XOR(v6, vB) % 2^32 / 2^12 + v6 = v6 % 1 * (2^32 - 1) + v6 + v1 = v1 + v6 + W[perm_blake3[j + 6]] + vC = XOR(vC, v1) % 2^32 / 2^8 + vC = vC % 1 * (2^32 - 1) + vC + vB = vB + vC + v6 = XOR(v6, vB) % 2^32 / 2^7 + v6 = v6 % 1 * (2^32 - 1) + v6 + v2 = v2 + v7 + W[perm_blake3[j + 4]] + vD = XOR(vD, v2) % 2^32 / 2^16 + vD = vD % 1 * (2^32 - 1) + vD + v8 = v8 + vD + v7 = XOR(v7, v8) % 2^32 / 2^12 + v7 = v7 % 1 * (2^32 - 1) + v7 + v2 = v2 + v7 + W[perm_blake3[j + 18]] + vD = XOR(vD, v2) % 2^32 / 2^8 + vD = vD % 1 * (2^32 - 1) + vD + v8 = v8 + vD + v7 = XOR(v7, v8) % 2^32 / 2^7 + v7 = v7 % 1 * (2^32 - 1) + v7 + v3 = v3 + v4 + W[perm_blake3[j + 19]] + vE = XOR(vE, v3) % 2^32 / 2^16 + vE = vE % 1 * (2^32 - 1) + vE + v9 = v9 + vE + v4 = XOR(v4, v9) % 2^32 / 2^12 + v4 = v4 % 1 * (2^32 - 1) + v4 + v3 = v3 + v4 + W[perm_blake3[j + 20]] + vE = XOR(vE, v3) % 2^32 / 2^8 + vE = vE % 1 * (2^32 - 1) + vE + v9 = v9 + vE + v4 = XOR(v4, v9) % 2^32 / 2^7 + v4 = v4 % 1 * (2^32 - 1) + v4 + end + if wide_output then + H_out[ 9] = XOR(h1, v8) + H_out[10] = XOR(h2, v9) + H_out[11] = XOR(h3, vA) + H_out[12] = XOR(h4, vB) + H_out[13] = XOR(h5, vC) + H_out[14] = XOR(h6, vD) + H_out[15] = XOR(h7, vE) + H_out[16] = XOR(h8, vF) + end + h1 = XOR(v0, v8) + h2 = XOR(v1, v9) + h3 = XOR(v2, vA) + h4 = XOR(v3, vB) + h5 = XOR(v4, vC) + h6 = XOR(v5, vD) + h7 = XOR(v6, vE) + h8 = XOR(v7, vF) + end + H_out[1], H_out[2], H_out[3], H_out[4], H_out[5], H_out[6], H_out[7], H_out[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + +end + + +-------------------------------------------------------------------------------- +-- MAGIC NUMBERS CALCULATOR +-------------------------------------------------------------------------------- +-- Q: +-- Is 53-bit "double" math enough to calculate square roots and cube roots of primes with 64 correct bits after decimal point? +-- A: +-- Yes, 53-bit "double" arithmetic is enough. +-- We could obtain first 40 bits by direct calculation of p^(1/3) and next 40 bits by one step of Newton's method. + +do + local function mul(src1, src2, factor, result_length) + -- src1, src2 - long integers (arrays of digits in base 2^24) + -- factor - small integer + -- returns long integer result (src1 * src2 * factor) and its floating point approximation + local result, carry, value, weight = {}, 0.0, 0.0, 1.0 + for j = 1, result_length do + for k = math_max(1, j + 1 - #src2), math_min(j, #src1) do + carry = carry + factor * src1[k] * src2[j + 1 - k] -- "int32" is not enough for multiplication result, that's why "factor" must be of type "double" + end + local digit = carry % 2^24 + result[j] = floor(digit) + carry = (carry - digit) / 2^24 + value = value + digit * weight + weight = weight * 2^24 + end + return result, value + end + + local idx, step, p, one, sqrt_hi, sqrt_lo = 0, {4, 1, 2, -2, 2}, 4, {1}, sha2_H_hi, sha2_H_lo + repeat + p = p + step[p % 6] + local d = 1 + repeat + d = d + step[d % 6] + if d*d > p then -- next prime number is found + local root = p^(1/3) + local R = root * 2^40 + R = mul({R - R % 1}, one, 1.0, 2) + local _, delta = mul(R, mul(R, R, 1.0, 4), -1.0, 4) + local hi = R[2] % 65536 * 65536 + floor(R[1] / 256) + local lo = R[1] % 256 * 16777216 + floor(delta * (2^-56 / 3) * root / p) + if idx < 16 then + root = p^(1/2) + R = root * 2^40 + R = mul({R - R % 1}, one, 1.0, 2) + _, delta = mul(R, R, -1.0, 2) + local hi = R[2] % 65536 * 65536 + floor(R[1] / 256) + local lo = R[1] % 256 * 16777216 + floor(delta * 2^-17 / root) + local idx = idx % 8 + 1 + sha2_H_ext256[224][idx] = lo + sqrt_hi[idx], sqrt_lo[idx] = hi, lo + hi * hi_factor + if idx > 7 then + sqrt_hi, sqrt_lo = sha2_H_ext512_hi[384], sha2_H_ext512_lo[384] + end + end + idx = idx + 1 + sha2_K_hi[idx], sha2_K_lo[idx] = hi, lo % K_lo_modulo + hi * hi_factor + break + end + until p % d == 0 + until idx > 79 +end + +-- Calculating IVs for SHA512/224 and SHA512/256 +for width = 224, 256, 32 do + local H_lo, H_hi = {} + if HEX64 then + for j = 1, 8 do + H_lo[j] = XORA5(sha2_H_lo[j]) + end + else + H_hi = {} + for j = 1, 8 do + H_lo[j] = XORA5(sha2_H_lo[j]) + H_hi[j] = XORA5(sha2_H_hi[j]) + end + end + sha512_feed_128(H_lo, H_hi, "SHA-512/"..tostring(width).."\128"..string_rep("\0", 115).."\88", 0, 128) + sha2_H_ext512_lo[width] = H_lo + sha2_H_ext512_hi[width] = H_hi +end + +-- Constants for MD5 +do + local sin, abs, modf = math.sin, math.abs, math.modf + for idx = 1, 64 do + -- we can't use formula floor(abs(sin(idx))*2^32) because its result may be beyond integer range on Lua built with 32-bit integers + local hi, lo = modf(abs(sin(idx)) * 2^16) + md5_K[idx] = hi * 65536 + floor(lo * 2^16) + end +end + +-- Constants for SHA-3 +do + local sh_reg = 29 + + local function next_bit() + local r = sh_reg % 2 + sh_reg = XOR_BYTE((sh_reg - r) / 2, 142 * r) + return r + end + + for idx = 1, 24 do + local lo, m = 0 + for _ = 1, 6 do + m = m and m * m * 2 or 1 + lo = lo + next_bit() * m + end + local hi = next_bit() * m + sha3_RC_hi[idx], sha3_RC_lo[idx] = hi, lo + hi * hi_factor_keccak + end +end + +if branch == "FFI" then + sha2_K_hi = ffi.new("uint32_t[?]", #sha2_K_hi + 1, 0, unpack(sha2_K_hi)) + sha2_K_lo = ffi.new("int64_t[?]", #sha2_K_lo + 1, 0, unpack(sha2_K_lo)) + --md5_K = ffi.new("uint32_t[?]", #md5_K + 1, 0, unpack(md5_K)) + if hi_factor_keccak == 0 then + sha3_RC_lo = ffi.new("uint32_t[?]", #sha3_RC_lo + 1, 0, unpack(sha3_RC_lo)) + sha3_RC_hi = ffi.new("uint32_t[?]", #sha3_RC_hi + 1, 0, unpack(sha3_RC_hi)) + else + sha3_RC_lo = ffi.new("int64_t[?]", #sha3_RC_lo + 1, 0, unpack(sha3_RC_lo)) + end +end + + +-------------------------------------------------------------------------------- +-- MAIN FUNCTIONS +-------------------------------------------------------------------------------- + +local function sha256ext(width, message) + -- Create an instance (private objects for current calculation) + local H, length, tail = {unpack(sha2_H_ext256[width])}, 0.0, "" + + local function partial(message_part) + if message_part then + if tail then + length = length + #message_part + local offs = 0 + if tail ~= "" and #tail + #message_part >= 64 then + offs = 64 - #tail + sha256_feed_64(H, tail..sub(message_part, 1, offs), 0, 64) + tail = "" + end + local size = #message_part - offs + local size_tail = size % 64 + sha256_feed_64(H, message_part, offs, size - size_tail) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + local final_blocks = {tail, "\128", string_rep("\0", (-9 - length) % 64 + 1)} + tail = nil + -- Assuming user data length is shorter than (2^53)-9 bytes + -- Anyway, it looks very unrealistic that someone would spend more than a year of calculations to process 2^53 bytes of data by using this Lua script :-) + -- 2^53 bytes = 2^56 bits, so "bit-counter" fits in 7 bytes + length = length * (8 / 256^7) -- convert "byte-counter" to "bit-counter" and move decimal point to the left + for j = 4, 10 do + length = length % 1 * 256 + final_blocks[j] = char(floor(length)) + end + final_blocks = table_concat(final_blocks) + sha256_feed_64(H, final_blocks, 0, #final_blocks) + local max_reg = width / 32 + for j = 1, max_reg do + H[j] = HEX(H[j]) + end + H = table_concat(H, "", 1, max_reg) + end + return H + end + end + + if message then + -- Actually perform calculations and return the SHA256 digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get SHA256 digest by invoking this function without an argument + return partial + end +end + + +local function sha512ext(width, message) + -- Create an instance (private objects for current calculation) + local length, tail, H_lo, H_hi = 0.0, "", {unpack(sha2_H_ext512_lo[width])}, not HEX64 and {unpack(sha2_H_ext512_hi[width])} + + local function partial(message_part) + if message_part then + if tail then + length = length + #message_part + local offs = 0 + if tail ~= "" and #tail + #message_part >= 128 then + offs = 128 - #tail + sha512_feed_128(H_lo, H_hi, tail..sub(message_part, 1, offs), 0, 128) + tail = "" + end + local size = #message_part - offs + local size_tail = size % 128 + sha512_feed_128(H_lo, H_hi, message_part, offs, size - size_tail) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + local final_blocks = {tail, "\128", string_rep("\0", (-17-length) % 128 + 9)} + tail = nil + -- Assuming user data length is shorter than (2^53)-17 bytes + -- 2^53 bytes = 2^56 bits, so "bit-counter" fits in 7 bytes + length = length * (8 / 256^7) -- convert "byte-counter" to "bit-counter" and move floating point to the left + for j = 4, 10 do + length = length % 1 * 256 + final_blocks[j] = char(floor(length)) + end + final_blocks = table_concat(final_blocks) + sha512_feed_128(H_lo, H_hi, final_blocks, 0, #final_blocks) + local max_reg = ceil(width / 64) + if HEX64 then + for j = 1, max_reg do + H_lo[j] = HEX64(H_lo[j]) + end + else + for j = 1, max_reg do + H_lo[j] = HEX(H_hi[j])..HEX(H_lo[j]) + end + H_hi = nil + end + H_lo = sub(table_concat(H_lo, "", 1, max_reg), 1, width / 4) + end + return H_lo + end + end + + if message then + -- Actually perform calculations and return the SHA512 digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get SHA512 digest by invoking this function without an argument + return partial + end +end + + +local function md5(message) + -- Create an instance (private objects for current calculation) + local H, length, tail = {unpack(md5_sha1_H, 1, 4)}, 0.0, "" + + local function partial(message_part) + if message_part then + if tail then + length = length + #message_part + local offs = 0 + if tail ~= "" and #tail + #message_part >= 64 then + offs = 64 - #tail + md5_feed_64(H, tail..sub(message_part, 1, offs), 0, 64) + tail = "" + end + local size = #message_part - offs + local size_tail = size % 64 + md5_feed_64(H, message_part, offs, size - size_tail) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + local final_blocks = {tail, "\128", string_rep("\0", (-9 - length) % 64)} + tail = nil + length = length * 8 -- convert "byte-counter" to "bit-counter" + for j = 4, 11 do + local low_byte = length % 256 + final_blocks[j] = char(low_byte) + length = (length - low_byte) / 256 + end + final_blocks = table_concat(final_blocks) + md5_feed_64(H, final_blocks, 0, #final_blocks) + for j = 1, 4 do + H[j] = HEX(H[j]) + end + H = gsub(table_concat(H), "(..)(..)(..)(..)", "%4%3%2%1") + end + return H + end + end + + if message then + -- Actually perform calculations and return the MD5 digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get MD5 digest by invoking this function without an argument + return partial + end +end + + +local function sha1(message) + -- Create an instance (private objects for current calculation) + local H, length, tail = {unpack(md5_sha1_H)}, 0.0, "" + + local function partial(message_part) + if message_part then + if tail then + length = length + #message_part + local offs = 0 + if tail ~= "" and #tail + #message_part >= 64 then + offs = 64 - #tail + sha1_feed_64(H, tail..sub(message_part, 1, offs), 0, 64) + tail = "" + end + local size = #message_part - offs + local size_tail = size % 64 + sha1_feed_64(H, message_part, offs, size - size_tail) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + local final_blocks = {tail, "\128", string_rep("\0", (-9 - length) % 64 + 1)} + tail = nil + -- Assuming user data length is shorter than (2^53)-9 bytes + -- 2^53 bytes = 2^56 bits, so "bit-counter" fits in 7 bytes + length = length * (8 / 256^7) -- convert "byte-counter" to "bit-counter" and move decimal point to the left + for j = 4, 10 do + length = length % 1 * 256 + final_blocks[j] = char(floor(length)) + end + final_blocks = table_concat(final_blocks) + sha1_feed_64(H, final_blocks, 0, #final_blocks) + for j = 1, 5 do + H[j] = HEX(H[j]) + end + H = table_concat(H) + end + return H + end + end + + if message then + -- Actually perform calculations and return the SHA-1 digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get SHA-1 digest by invoking this function without an argument + return partial + end +end + + +local function keccak(block_size_in_bytes, digest_size_in_bytes, is_SHAKE, message) + -- "block_size_in_bytes" is multiple of 8 + if type(digest_size_in_bytes) ~= "number" then + -- arguments in SHAKE are swapped: + -- NIST FIPS 202 defines SHAKE(message,num_bits) + -- this module defines SHAKE(num_bytes,message) + -- it's easy to forget about this swap, hence the check + error("Argument 'digest_size_in_bytes' must be a number", 2) + end + -- Create an instance (private objects for current calculation) + local tail, lanes_lo, lanes_hi = "", create_array_of_lanes(), hi_factor_keccak == 0 and create_array_of_lanes() + local result + + local function partial(message_part) + if message_part then + if tail then + local offs = 0 + if tail ~= "" and #tail + #message_part >= block_size_in_bytes then + offs = block_size_in_bytes - #tail + keccak_feed(lanes_lo, lanes_hi, tail..sub(message_part, 1, offs), 0, block_size_in_bytes, block_size_in_bytes) + tail = "" + end + local size = #message_part - offs + local size_tail = size % block_size_in_bytes + keccak_feed(lanes_lo, lanes_hi, message_part, offs, size - size_tail, block_size_in_bytes) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + -- append the following bits to the message: for usual SHA-3: 011(0*)1, for SHAKE: 11111(0*)1 + local gap_start = is_SHAKE and 31 or 6 + tail = tail..(#tail + 1 == block_size_in_bytes and char(gap_start + 128) or char(gap_start)..string_rep("\0", (-2 - #tail) % block_size_in_bytes).."\128") + keccak_feed(lanes_lo, lanes_hi, tail, 0, #tail, block_size_in_bytes) + tail = nil + local lanes_used = 0 + local total_lanes = floor(block_size_in_bytes / 8) + local qwords = {} + + local function get_next_qwords_of_digest(qwords_qty) + -- returns not more than 'qwords_qty' qwords ('qwords_qty' might be non-integer) + -- doesn't go across keccak-buffer boundary + -- block_size_in_bytes is a multiple of 8, so, keccak-buffer contains integer number of qwords + if lanes_used >= total_lanes then + keccak_feed(lanes_lo, lanes_hi, "\0\0\0\0\0\0\0\0", 0, 8, 8) + lanes_used = 0 + end + qwords_qty = floor(math_min(qwords_qty, total_lanes - lanes_used)) + if hi_factor_keccak ~= 0 then + for j = 1, qwords_qty do + qwords[j] = HEX64(lanes_lo[lanes_used + j - 1 + lanes_index_base]) + end + else + for j = 1, qwords_qty do + qwords[j] = HEX(lanes_hi[lanes_used + j])..HEX(lanes_lo[lanes_used + j]) + end + end + lanes_used = lanes_used + qwords_qty + return + gsub(table_concat(qwords, "", 1, qwords_qty), "(..)(..)(..)(..)(..)(..)(..)(..)", "%8%7%6%5%4%3%2%1"), + qwords_qty * 8 + end + + local parts = {} -- digest parts + local last_part, last_part_size = "", 0 + + local function get_next_part_of_digest(bytes_needed) + -- returns 'bytes_needed' bytes, for arbitrary integer 'bytes_needed' + bytes_needed = bytes_needed or 1 + if bytes_needed <= last_part_size then + last_part_size = last_part_size - bytes_needed + local part_size_in_nibbles = bytes_needed * 2 + local result = sub(last_part, 1, part_size_in_nibbles) + last_part = sub(last_part, part_size_in_nibbles + 1) + return result + end + local parts_qty = 0 + if last_part_size > 0 then + parts_qty = 1 + parts[parts_qty] = last_part + bytes_needed = bytes_needed - last_part_size + end + -- repeats until the length is enough + while bytes_needed >= 8 do + local next_part, next_part_size = get_next_qwords_of_digest(bytes_needed / 8) + parts_qty = parts_qty + 1 + parts[parts_qty] = next_part + bytes_needed = bytes_needed - next_part_size + end + if bytes_needed > 0 then + last_part, last_part_size = get_next_qwords_of_digest(1) + parts_qty = parts_qty + 1 + parts[parts_qty] = get_next_part_of_digest(bytes_needed) + else + last_part, last_part_size = "", 0 + end + return table_concat(parts, "", 1, parts_qty) + end + + if digest_size_in_bytes < 0 then + result = get_next_part_of_digest + else + result = get_next_part_of_digest(digest_size_in_bytes) + end + end + return result + end + end + + if message then + -- Actually perform calculations and return the SHA-3 digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get SHA-3 digest by invoking this function without an argument + return partial + end +end + + +local hex_to_bin, bin_to_hex, bin_to_base64, base64_to_bin +do + function hex_to_bin(hex_string) + return (gsub(hex_string, "%x%x", + function (hh) + return char(tonumber(hh, 16)) + end + )) + end + + function bin_to_hex(binary_string) + return (gsub(binary_string, ".", + function (c) + return string_format("%02x", byte(c)) + end + )) + end + + local base64_symbols = { + ['+'] = 62, ['-'] = 62, [62] = '+', + ['/'] = 63, ['_'] = 63, [63] = '/', + ['='] = -1, ['.'] = -1, [-1] = '=' + } + local symbol_index = 0 + for j, pair in ipairs{'AZ', 'az', '09'} do + for ascii = byte(pair), byte(pair, 2) do + local ch = char(ascii) + base64_symbols[ch] = symbol_index + base64_symbols[symbol_index] = ch + symbol_index = symbol_index + 1 + end + end + + function bin_to_base64(binary_string) + local result = {} + for pos = 1, #binary_string, 3 do + local c1, c2, c3, c4 = byte(sub(binary_string, pos, pos + 2)..'\0', 1, -1) + result[#result + 1] = + base64_symbols[floor(c1 / 4)] + ..base64_symbols[c1 % 4 * 16 + floor(c2 / 16)] + ..base64_symbols[c3 and c2 % 16 * 4 + floor(c3 / 64) or -1] + ..base64_symbols[c4 and c3 % 64 or -1] + end + return table_concat(result) + end + + function base64_to_bin(base64_string) + local result, chars_qty = {}, 3 + for pos, ch in gmatch(gsub(base64_string, '%s+', ''), '()(.)') do + local code = base64_symbols[ch] + if code < 0 then + chars_qty = chars_qty - 1 + code = 0 + end + local idx = pos % 4 + if idx > 0 then + result[-idx] = code + else + local c1 = result[-1] * 4 + floor(result[-2] / 16) + local c2 = (result[-2] % 16) * 16 + floor(result[-3] / 4) + local c3 = (result[-3] % 4) * 64 + code + result[#result + 1] = sub(char(c1, c2, c3), 1, chars_qty) + end + end + return table_concat(result) + end + +end + + +local block_size_for_HMAC -- this table will be initialized at the end of the module + +local function pad_and_xor(str, result_length, byte_for_xor) + return gsub(str, ".", + function(c) + return char(XOR_BYTE(byte(c), byte_for_xor)) + end + )..string_rep(char(byte_for_xor), result_length - #str) +end + +local function hmac(hash_func, key, message) + -- Create an instance (private objects for current calculation) + local block_size = block_size_for_HMAC[hash_func] + if not block_size then + error("Unknown hash function", 2) + end + if #key > block_size then + key = hex_to_bin(hash_func(key)) + end + local append = hash_func()(pad_and_xor(key, block_size, 0x36)) + local result + + local function partial(message_part) + if not message_part then + result = result or hash_func(pad_and_xor(key, block_size, 0x5C)..hex_to_bin(append())) + return result + elseif result then + error("Adding more chunks is not allowed after receiving the result", 2) + else + append(message_part) + return partial + end + end + + if message then + -- Actually perform calculations and return the HMAC of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading of a message + -- User should feed every chunk of the message as single argument to this function and finally get HMAC by invoking this function without an argument + return partial + end +end + + +local function xor_blake2_salt(salt, letter, H_lo, H_hi) + -- salt: concatenation of "Salt"+"Personalization" fields + local max_size = letter == "s" and 16 or 32 + local salt_size = #salt + if salt_size > max_size then + error(string_format("For BLAKE2%s/BLAKE2%sp/BLAKE2X%s the 'salt' parameter length must not exceed %d bytes", letter, letter, letter, max_size), 2) + end + if H_lo then + local offset, blake2_word_size, xor = 0, letter == "s" and 4 or 8, letter == "s" and XOR or XORA5 + for j = 5, 4 + ceil(salt_size / blake2_word_size) do + local prev, last + for _ = 1, blake2_word_size, 4 do + offset = offset + 4 + local a, b, c, d = byte(salt, offset - 3, offset) + local four_bytes = (((d or 0) * 256 + (c or 0)) * 256 + (b or 0)) * 256 + (a or 0) + prev, last = last, four_bytes + end + H_lo[j] = xor(H_lo[j], prev and last * hi_factor + prev or last) + if H_hi then + H_hi[j] = xor(H_hi[j], last) + end + end + end +end + +local function blake2s(message, key, salt, digest_size_in_bytes, XOF_length, B2_offset) + -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode) + -- key: (optional) binary string up to 32 bytes, by default empty string + -- salt: (optional) binary string up to 16 bytes, by default empty string + -- digest_size_in_bytes: (optional) integer from 1 to 32, by default 32 + -- The last two parameters "XOF_length" and "B2_offset" are for internal use only, user must omit them (or pass nil) + digest_size_in_bytes = digest_size_in_bytes or 32 + if digest_size_in_bytes < 1 or digest_size_in_bytes > 32 then + error("BLAKE2s digest length must be from 1 to 32 bytes", 2) + end + key = key or "" + local key_length = #key + if key_length > 32 then + error("BLAKE2s key length must not exceed 32 bytes", 2) + end + salt = salt or "" + local bytes_compressed, tail, H = 0.0, "", {unpack(sha2_H_hi)} + if B2_offset then + H[1] = XOR(H[1], digest_size_in_bytes) + H[2] = XOR(H[2], 0x20) + H[3] = XOR(H[3], B2_offset) + H[4] = XOR(H[4], 0x20000000 + XOF_length) + else + H[1] = XOR(H[1], 0x01010000 + key_length * 256 + digest_size_in_bytes) + if XOF_length then + H[4] = XOR(H[4], XOF_length) + end + end + if salt ~= "" then + xor_blake2_salt(salt, "s", H) + end + + local function partial(message_part) + if message_part then + if tail then + local offs = 0 + if tail ~= "" and #tail + #message_part > 64 then + offs = 64 - #tail + bytes_compressed = blake2s_feed_64(H, tail..sub(message_part, 1, offs), 0, 64, bytes_compressed) + tail = "" + end + local size = #message_part - offs + local size_tail = size > 0 and (size - 1) % 64 + 1 or 0 + bytes_compressed = blake2s_feed_64(H, message_part, offs, size - size_tail, bytes_compressed) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + if B2_offset then + blake2s_feed_64(H, nil, 0, 64, 0, 32) + else + blake2s_feed_64(H, tail..string_rep("\0", 64 - #tail), 0, 64, bytes_compressed, #tail) + end + tail = nil + if not XOF_length or B2_offset then + local max_reg = ceil(digest_size_in_bytes / 4) + for j = 1, max_reg do + H[j] = HEX(H[j]) + end + H = sub(gsub(table_concat(H, "", 1, max_reg), "(..)(..)(..)(..)", "%4%3%2%1"), 1, digest_size_in_bytes * 2) + end + end + return H + end + end + + if key_length > 0 then + partial(key..string_rep("\0", 64 - key_length)) + end + if B2_offset then + return partial() + elseif message then + -- Actually perform calculations and return the BLAKE2s digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get BLAKE2s digest by invoking this function without an argument + return partial + end +end + +local function blake2b(message, key, salt, digest_size_in_bytes, XOF_length, B2_offset) + -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode) + -- key: (optional) binary string up to 64 bytes, by default empty string + -- salt: (optional) binary string up to 32 bytes, by default empty string + -- digest_size_in_bytes: (optional) integer from 1 to 64, by default 64 + -- The last two parameters "XOF_length" and "B2_offset" are for internal use only, user must omit them (or pass nil) + digest_size_in_bytes = floor(digest_size_in_bytes or 64) + if digest_size_in_bytes < 1 or digest_size_in_bytes > 64 then + error("BLAKE2b digest length must be from 1 to 64 bytes", 2) + end + key = key or "" + local key_length = #key + if key_length > 64 then + error("BLAKE2b key length must not exceed 64 bytes", 2) + end + salt = salt or "" + local bytes_compressed, tail, H_lo, H_hi = 0.0, "", {unpack(sha2_H_lo)}, not HEX64 and {unpack(sha2_H_hi)} + if B2_offset then + if H_hi then + H_lo[1] = XORA5(H_lo[1], digest_size_in_bytes) + H_hi[1] = XORA5(H_hi[1], 0x40) + H_lo[2] = XORA5(H_lo[2], B2_offset) + H_hi[2] = XORA5(H_hi[2], XOF_length) + else + H_lo[1] = XORA5(H_lo[1], 0x40 * hi_factor + digest_size_in_bytes) + H_lo[2] = XORA5(H_lo[2], XOF_length * hi_factor + B2_offset) + end + H_lo[3] = XORA5(H_lo[3], 0x4000) + else + H_lo[1] = XORA5(H_lo[1], 0x01010000 + key_length * 256 + digest_size_in_bytes) + if XOF_length then + if H_hi then + H_hi[2] = XORA5(H_hi[2], XOF_length) + else + H_lo[2] = XORA5(H_lo[2], XOF_length * hi_factor) + end + end + end + if salt ~= "" then + xor_blake2_salt(salt, "b", H_lo, H_hi) + end + + local function partial(message_part) + if message_part then + if tail then + local offs = 0 + if tail ~= "" and #tail + #message_part > 128 then + offs = 128 - #tail + bytes_compressed = blake2b_feed_128(H_lo, H_hi, tail..sub(message_part, 1, offs), 0, 128, bytes_compressed) + tail = "" + end + local size = #message_part - offs + local size_tail = size > 0 and (size - 1) % 128 + 1 or 0 + bytes_compressed = blake2b_feed_128(H_lo, H_hi, message_part, offs, size - size_tail, bytes_compressed) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + if B2_offset then + blake2b_feed_128(H_lo, H_hi, nil, 0, 128, 0, 64) + else + blake2b_feed_128(H_lo, H_hi, tail..string_rep("\0", 128 - #tail), 0, 128, bytes_compressed, #tail) + end + tail = nil + if XOF_length and not B2_offset then + if H_hi then + for j = 8, 1, -1 do + H_lo[j*2] = H_hi[j] + H_lo[j*2-1] = H_lo[j] + end + return H_lo, 16 + end + else + local max_reg = ceil(digest_size_in_bytes / 8) + if H_hi then + for j = 1, max_reg do + H_lo[j] = HEX(H_hi[j])..HEX(H_lo[j]) + end + else + for j = 1, max_reg do + H_lo[j] = HEX64(H_lo[j]) + end + end + H_lo = sub(gsub(table_concat(H_lo, "", 1, max_reg), "(..)(..)(..)(..)(..)(..)(..)(..)", "%8%7%6%5%4%3%2%1"), 1, digest_size_in_bytes * 2) + end + H_hi = nil + end + return H_lo + end + end + + if key_length > 0 then + partial(key..string_rep("\0", 128 - key_length)) + end + if B2_offset then + return partial() + elseif message then + -- Actually perform calculations and return the BLAKE2b digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get BLAKE2b digest by invoking this function without an argument + return partial + end +end + +local function blake2sp(message, key, salt, digest_size_in_bytes) + -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode) + -- key: (optional) binary string up to 32 bytes, by default empty string + -- salt: (optional) binary string up to 16 bytes, by default empty string + -- digest_size_in_bytes: (optional) integer from 1 to 32, by default 32 + digest_size_in_bytes = digest_size_in_bytes or 32 + if digest_size_in_bytes < 1 or digest_size_in_bytes > 32 then + error("BLAKE2sp digest length must be from 1 to 32 bytes", 2) + end + key = key or "" + local key_length = #key + if key_length > 32 then + error("BLAKE2sp key length must not exceed 32 bytes", 2) + end + salt = salt or "" + local instances, length, first_dword_of_parameter_block, result = {}, 0.0, 0x02080000 + key_length * 256 + digest_size_in_bytes + for j = 1, 8 do + local bytes_compressed, tail, H = 0.0, "", {unpack(sha2_H_hi)} + instances[j] = {bytes_compressed, tail, H} + H[1] = XOR(H[1], first_dword_of_parameter_block) + H[3] = XOR(H[3], j-1) + H[4] = XOR(H[4], 0x20000000) + if salt ~= "" then + xor_blake2_salt(salt, "s", H) + end + end + + local function partial(message_part) + if message_part then + if instances then + local from = 0 + while true do + local to = math_min(from + 64 - length % 64, #message_part) + if to > from then + local inst = instances[floor(length / 64) % 8 + 1] + local part = sub(message_part, from + 1, to) + length, from = length + to - from, to + local bytes_compressed, tail = inst[1], inst[2] + if #tail < 64 then + tail = tail..part + else + local H = inst[3] + bytes_compressed = blake2s_feed_64(H, tail, 0, 64, bytes_compressed) + tail = part + end + inst[1], inst[2] = bytes_compressed, tail + else + break + end + end + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if instances then + local root_H = {unpack(sha2_H_hi)} + root_H[1] = XOR(root_H[1], first_dword_of_parameter_block) + root_H[4] = XOR(root_H[4], 0x20010000) + if salt ~= "" then + xor_blake2_salt(salt, "s", root_H) + end + for j = 1, 8 do + local inst = instances[j] + local bytes_compressed, tail, H = inst[1], inst[2], inst[3] + blake2s_feed_64(H, tail..string_rep("\0", 64 - #tail), 0, 64, bytes_compressed, #tail, j == 8) + if j % 2 == 0 then + local index = 0 + for k = j - 1, j do + local inst = instances[k] + local H = inst[3] + for i = 1, 8 do + index = index + 1 + common_W_blake2s[index] = H[i] + end + end + blake2s_feed_64(root_H, nil, 0, 64, 64 * (j/2 - 1), j == 8 and 64, j == 8) + end + end + instances = nil + local max_reg = ceil(digest_size_in_bytes / 4) + for j = 1, max_reg do + root_H[j] = HEX(root_H[j]) + end + result = sub(gsub(table_concat(root_H, "", 1, max_reg), "(..)(..)(..)(..)", "%4%3%2%1"), 1, digest_size_in_bytes * 2) + end + return result + end + end + + if key_length > 0 then + key = key..string_rep("\0", 64 - key_length) + for j = 1, 8 do + partial(key) + end + end + if message then + -- Actually perform calculations and return the BLAKE2sp digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get BLAKE2sp digest by invoking this function without an argument + return partial + end + +end + +local function blake2bp(message, key, salt, digest_size_in_bytes) + -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode) + -- key: (optional) binary string up to 64 bytes, by default empty string + -- salt: (optional) binary string up to 32 bytes, by default empty string + -- digest_size_in_bytes: (optional) integer from 1 to 64, by default 64 + digest_size_in_bytes = digest_size_in_bytes or 64 + if digest_size_in_bytes < 1 or digest_size_in_bytes > 64 then + error("BLAKE2bp digest length must be from 1 to 64 bytes", 2) + end + key = key or "" + local key_length = #key + if key_length > 64 then + error("BLAKE2bp key length must not exceed 64 bytes", 2) + end + salt = salt or "" + local instances, length, first_dword_of_parameter_block, result = {}, 0.0, 0x02040000 + key_length * 256 + digest_size_in_bytes + for j = 1, 4 do + local bytes_compressed, tail, H_lo, H_hi = 0.0, "", {unpack(sha2_H_lo)}, not HEX64 and {unpack(sha2_H_hi)} + instances[j] = {bytes_compressed, tail, H_lo, H_hi} + H_lo[1] = XORA5(H_lo[1], first_dword_of_parameter_block) + H_lo[2] = XORA5(H_lo[2], j-1) + H_lo[3] = XORA5(H_lo[3], 0x4000) + if salt ~= "" then + xor_blake2_salt(salt, "b", H_lo, H_hi) + end + end + + local function partial(message_part) + if message_part then + if instances then + local from = 0 + while true do + local to = math_min(from + 128 - length % 128, #message_part) + if to > from then + local inst = instances[floor(length / 128) % 4 + 1] + local part = sub(message_part, from + 1, to) + length, from = length + to - from, to + local bytes_compressed, tail = inst[1], inst[2] + if #tail < 128 then + tail = tail..part + else + local H_lo, H_hi = inst[3], inst[4] + bytes_compressed = blake2b_feed_128(H_lo, H_hi, tail, 0, 128, bytes_compressed) + tail = part + end + inst[1], inst[2] = bytes_compressed, tail + else + break + end + end + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if instances then + local root_H_lo, root_H_hi = {unpack(sha2_H_lo)}, not HEX64 and {unpack(sha2_H_hi)} + root_H_lo[1] = XORA5(root_H_lo[1], first_dword_of_parameter_block) + root_H_lo[3] = XORA5(root_H_lo[3], 0x4001) + if salt ~= "" then + xor_blake2_salt(salt, "b", root_H_lo, root_H_hi) + end + for j = 1, 4 do + local inst = instances[j] + local bytes_compressed, tail, H_lo, H_hi = inst[1], inst[2], inst[3], inst[4] + blake2b_feed_128(H_lo, H_hi, tail..string_rep("\0", 128 - #tail), 0, 128, bytes_compressed, #tail, j == 4) + if j % 2 == 0 then + local index = 0 + for k = j - 1, j do + local inst = instances[k] + local H_lo, H_hi = inst[3], inst[4] + for i = 1, 8 do + index = index + 1 + common_W_blake2b[index] = H_lo[i] + if H_hi then + index = index + 1 + common_W_blake2b[index] = H_hi[i] + end + end + end + blake2b_feed_128(root_H_lo, root_H_hi, nil, 0, 128, 128 * (j/2 - 1), j == 4 and 128, j == 4) + end + end + instances = nil + local max_reg = ceil(digest_size_in_bytes / 8) + if HEX64 then + for j = 1, max_reg do + root_H_lo[j] = HEX64(root_H_lo[j]) + end + else + for j = 1, max_reg do + root_H_lo[j] = HEX(root_H_hi[j])..HEX(root_H_lo[j]) + end + end + result = sub(gsub(table_concat(root_H_lo, "", 1, max_reg), "(..)(..)(..)(..)(..)(..)(..)(..)", "%8%7%6%5%4%3%2%1"), 1, digest_size_in_bytes * 2) + end + return result + end + end + + if key_length > 0 then + key = key..string_rep("\0", 128 - key_length) + for j = 1, 4 do + partial(key) + end + end + if message then + -- Actually perform calculations and return the BLAKE2bp digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get BLAKE2bp digest by invoking this function without an argument + return partial + end + +end + +local function blake2x(inner_func, inner_func_letter, common_W_blake2, block_size, digest_size_in_bytes, message, key, salt) + local XOF_digest_length_limit, XOF_digest_length, chunk_by_chunk_output = 2^(block_size / 2) - 1 + if digest_size_in_bytes == -1 then -- infinite digest + digest_size_in_bytes = math_huge + XOF_digest_length = floor(XOF_digest_length_limit) + chunk_by_chunk_output = true + else + if digest_size_in_bytes < 0 then + digest_size_in_bytes = -1.0 * digest_size_in_bytes + chunk_by_chunk_output = true + end + XOF_digest_length = floor(digest_size_in_bytes) + if XOF_digest_length >= XOF_digest_length_limit then + error("Requested digest is too long. BLAKE2X"..inner_func_letter.." finite digest is limited by (2^"..floor(block_size / 2)..")-2 bytes. Hint: you can generate infinite digest.", 2) + end + end + salt = salt or "" + if salt ~= "" then + xor_blake2_salt(salt, inner_func_letter) -- don't xor, only check the size of salt + end + local inner_partial = inner_func(nil, key, salt, nil, XOF_digest_length) + local result + + local function partial(message_part) + if message_part then + if inner_partial then + inner_partial(message_part) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if inner_partial then + local half_W, half_W_size = inner_partial() + half_W_size, inner_partial = half_W_size or 8 + + local function get_hash_block(block_no) + -- block_no = 0...(2^32-1) + local size = math_min(block_size, digest_size_in_bytes - block_no * block_size) + if size <= 0 then + return "" + end + for j = 1, half_W_size do + common_W_blake2[j] = half_W[j] + end + for j = half_W_size + 1, 2 * half_W_size do + common_W_blake2[j] = 0 + end + return inner_func(nil, nil, salt, size, XOF_digest_length, floor(block_no)) + end + + local hash = {} + if chunk_by_chunk_output then + local pos, period, cached_block_no, cached_block = 0, block_size * 2^32 + + local function get_next_part_of_digest(arg1, arg2) + if arg1 == "seek" then + -- Usage #1: get_next_part_of_digest("seek", new_pos) + pos = arg2 % period + else + -- Usage #2: hex_string = get_next_part_of_digest(size) + local size, index = arg1 or 1, 0 + while size > 0 do + local block_offset = pos % block_size + local block_no = (pos - block_offset) / block_size + local part_size = math_min(size, block_size - block_offset) + if cached_block_no ~= block_no then + cached_block_no = block_no + cached_block = get_hash_block(block_no) + end + index = index + 1 + hash[index] = sub(cached_block, block_offset * 2 + 1, (block_offset + part_size) * 2) + size = size - part_size + pos = (pos + part_size) % period + end + return table_concat(hash, "", 1, index) + end + end + + result = get_next_part_of_digest + else + for j = 1.0, ceil(digest_size_in_bytes / block_size) do + hash[j] = get_hash_block(j - 1.0) + end + result = table_concat(hash) + end + end + return result + end + end + + if message then + -- Actually perform calculations and return the BLAKE2X digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get BLAKE2X digest by invoking this function without an argument + return partial + end +end + +local function blake2xs(digest_size_in_bytes, message, key, salt) + -- digest_size_in_bytes: + -- 0..65534 = get finite digest as single Lua string + -- (-1) = get infinite digest in "chunk-by-chunk" output mode + -- (-2)..(-65534) = get finite digest in "chunk-by-chunk" output mode + -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode) + -- key: (optional) binary string up to 32 bytes, by default empty string + -- salt: (optional) binary string up to 16 bytes, by default empty string + return blake2x(blake2s, "s", common_W_blake2s, 32, digest_size_in_bytes, message, key, salt) +end + +local function blake2xb(digest_size_in_bytes, message, key, salt) + -- digest_size_in_bytes: + -- 0..4294967294 = get finite digest as single Lua string + -- (-1) = get infinite digest in "chunk-by-chunk" output mode + -- (-2)..(-4294967294) = get finite digest in "chunk-by-chunk" output mode + -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode) + -- key: (optional) binary string up to 64 bytes, by default empty string + -- salt: (optional) binary string up to 32 bytes, by default empty string + return blake2x(blake2b, "b", common_W_blake2b, 64, digest_size_in_bytes, message, key, salt) +end + + +local function blake3(message, key, digest_size_in_bytes, message_flags, K, return_array) + -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode) + -- key: (optional) binary string up to 32 bytes, by default empty string + -- digest_size_in_bytes: (optional) by default 32 + -- 0,1,2,3,4,... = get finite digest as single Lua string + -- (-1) = get infinite digest in "chunk-by-chunk" output mode + -- -2,-3,-4,... = get finite digest in "chunk-by-chunk" output mode + -- The last three parameters "message_flags", "K" and "return_array" are for internal use only, user must omit them (or pass nil) + key = key or "" + digest_size_in_bytes = digest_size_in_bytes or 32 + message_flags = message_flags or 0 + if key == "" then + K = K or sha2_H_hi + else + local key_length = #key + if key_length > 32 then + error("BLAKE3 key length must not exceed 32 bytes", 2) + end + key = key..string_rep("\0", 32 - key_length) + K = {} + for j = 1, 8 do + local a, b, c, d = byte(key, 4*j-3, 4*j) + K[j] = ((d * 256 + c) * 256 + b) * 256 + a + end + message_flags = message_flags + 16 -- flag:KEYED_HASH + end + local tail, H, chunk_index, blocks_in_chunk, stack_size, stack = "", {}, 0, 0, 0, {} + local final_H_in, final_block_length, chunk_by_chunk_output, result, wide_output = K + local final_compression_flags = 3 -- flags:CHUNK_START,CHUNK_END + + local function feed_blocks(str, offs, size) + -- size >= 0, size is multiple of 64 + while size > 0 do + local part_size_in_blocks, block_flags, H_in = 1, 0, H + if blocks_in_chunk == 0 then + block_flags = 1 -- flag:CHUNK_START + H_in, final_H_in = K, H + final_compression_flags = 2 -- flag:CHUNK_END + elseif blocks_in_chunk == 15 then + block_flags = 2 -- flag:CHUNK_END + final_compression_flags = 3 -- flags:CHUNK_START,CHUNK_END + final_H_in = K + else + part_size_in_blocks = math_min(size / 64, 15 - blocks_in_chunk) + end + local part_size = part_size_in_blocks * 64 + blake3_feed_64(str, offs, part_size, message_flags + block_flags, chunk_index, H_in, H) + offs, size = offs + part_size, size - part_size + blocks_in_chunk = (blocks_in_chunk + part_size_in_blocks) % 16 + if blocks_in_chunk == 0 then + -- completing the currect chunk + chunk_index = chunk_index + 1.0 + local divider = 2.0 + while chunk_index % divider == 0 do + divider = divider * 2.0 + stack_size = stack_size - 8 + for j = 1, 8 do + common_W_blake2s[j] = stack[stack_size + j] + end + for j = 1, 8 do + common_W_blake2s[j + 8] = H[j] + end + blake3_feed_64(nil, 0, 64, message_flags + 4, 0, K, H) -- flag:PARENT + end + for j = 1, 8 do + stack[stack_size + j] = H[j] + end + stack_size = stack_size + 8 + end + end + end + + local function get_hash_block(block_no) + local size = math_min(64, digest_size_in_bytes - block_no * 64) + if block_no < 0 or size <= 0 then + return "" + end + if chunk_by_chunk_output then + for j = 1, 16 do + common_W_blake2s[j] = stack[j + 16] + end + end + blake3_feed_64(nil, 0, 64, final_compression_flags, block_no, final_H_in, stack, wide_output, final_block_length) + if return_array then + return stack + end + local max_reg = ceil(size / 4) + for j = 1, max_reg do + stack[j] = HEX(stack[j]) + end + return sub(gsub(table_concat(stack, "", 1, max_reg), "(..)(..)(..)(..)", "%4%3%2%1"), 1, size * 2) + end + + local function partial(message_part) + if message_part then + if tail then + local offs = 0 + if tail ~= "" and #tail + #message_part > 64 then + offs = 64 - #tail + feed_blocks(tail..sub(message_part, 1, offs), 0, 64) + tail = "" + end + local size = #message_part - offs + local size_tail = size > 0 and (size - 1) % 64 + 1 or 0 + feed_blocks(message_part, offs, size - size_tail) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + final_block_length = #tail + tail = tail..string_rep("\0", 64 - #tail) + if common_W_blake2s[0] then + for j = 1, 16 do + local a, b, c, d = byte(tail, 4*j-3, 4*j) + common_W_blake2s[j] = OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a) + end + else + for j = 1, 16 do + local a, b, c, d = byte(tail, 4*j-3, 4*j) + common_W_blake2s[j] = ((d * 256 + c) * 256 + b) * 256 + a + end + end + tail = nil + for stack_size = stack_size - 8, 0, -8 do + blake3_feed_64(nil, 0, 64, message_flags + final_compression_flags, chunk_index, final_H_in, H, nil, final_block_length) + chunk_index, final_block_length, final_H_in, final_compression_flags = 0, 64, K, 4 -- flag:PARENT + for j = 1, 8 do + common_W_blake2s[j] = stack[stack_size + j] + end + for j = 1, 8 do + common_W_blake2s[j + 8] = H[j] + end + end + final_compression_flags = message_flags + final_compression_flags + 8 -- flag:ROOT + if digest_size_in_bytes < 0 then + if digest_size_in_bytes == -1 then -- infinite digest + digest_size_in_bytes = math_huge + else + digest_size_in_bytes = -1.0 * digest_size_in_bytes + end + chunk_by_chunk_output = true + for j = 1, 16 do + stack[j + 16] = common_W_blake2s[j] + end + end + digest_size_in_bytes = math_min(2^53, digest_size_in_bytes) + wide_output = digest_size_in_bytes > 32 + if chunk_by_chunk_output then + local pos, cached_block_no, cached_block = 0.0 + + local function get_next_part_of_digest(arg1, arg2) + if arg1 == "seek" then + -- Usage #1: get_next_part_of_digest("seek", new_pos) + pos = arg2 * 1.0 + else + -- Usage #2: hex_string = get_next_part_of_digest(size) + local size, index = arg1 or 1, 32 + while size > 0 do + local block_offset = pos % 64 + local block_no = (pos - block_offset) / 64 + local part_size = math_min(size, 64 - block_offset) + if cached_block_no ~= block_no then + cached_block_no = block_no + cached_block = get_hash_block(block_no) + end + index = index + 1 + stack[index] = sub(cached_block, block_offset * 2 + 1, (block_offset + part_size) * 2) + size = size - part_size + pos = pos + part_size + end + return table_concat(stack, "", 33, index) + end + end + + result = get_next_part_of_digest + elseif digest_size_in_bytes <= 64 then + result = get_hash_block(0) + else + local last_block_no = ceil(digest_size_in_bytes / 64) - 1 + for block_no = 0.0, last_block_no do + stack[33 + block_no] = get_hash_block(block_no) + end + result = table_concat(stack, "", 33, 33 + last_block_no) + end + end + return result + end + end + + if message then + -- Actually perform calculations and return the BLAKE3 digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get BLAKE3 digest by invoking this function without an argument + return partial + end +end + +local function blake3_derive_key(key_material, context_string, derived_key_size_in_bytes) + -- key_material: (string) your source of entropy to derive a key from (for example, it can be a master password) + -- set to nil for feeding the key material in "chunk-by-chunk" input mode + -- context_string: (string) unique description of the derived key + -- digest_size_in_bytes: (optional) by default 32 + -- 0,1,2,3,4,... = get finite derived key as single Lua string + -- (-1) = get infinite derived key in "chunk-by-chunk" output mode + -- -2,-3,-4,... = get finite derived key in "chunk-by-chunk" output mode + if type(context_string) ~= "string" then + error("'context_string' parameter must be a Lua string", 2) + end + local K = blake3(context_string, nil, nil, 32, nil, true) -- flag:DERIVE_KEY_CONTEXT + return blake3(key_material, nil, derived_key_size_in_bytes, 64, K) -- flag:DERIVE_KEY_MATERIAL +end + + + +local sha = { + md5 = md5, -- MD5 + sha1 = sha1, -- SHA-1 + -- SHA-2 hash functions: + sha224 = function (message) return sha256ext(224, message) end, -- SHA-224 + sha256 = function (message) return sha256ext(256, message) end, -- SHA-256 + sha512_224 = function (message) return sha512ext(224, message) end, -- SHA-512/224 + sha512_256 = function (message) return sha512ext(256, message) end, -- SHA-512/256 + sha384 = function (message) return sha512ext(384, message) end, -- SHA-384 + sha512 = function (message) return sha512ext(512, message) end, -- SHA-512 + -- SHA-3 hash functions: + sha3_224 = function (message) return keccak((1600 - 2 * 224) / 8, 224 / 8, false, message) end, -- SHA3-224 + sha3_256 = function (message) return keccak((1600 - 2 * 256) / 8, 256 / 8, false, message) end, -- SHA3-256 + sha3_384 = function (message) return keccak((1600 - 2 * 384) / 8, 384 / 8, false, message) end, -- SHA3-384 + sha3_512 = function (message) return keccak((1600 - 2 * 512) / 8, 512 / 8, false, message) end, -- SHA3-512 + shake128 = function (digest_size_in_bytes, message) return keccak((1600 - 2 * 128) / 8, digest_size_in_bytes, true, message) end, -- SHAKE128 + shake256 = function (digest_size_in_bytes, message) return keccak((1600 - 2 * 256) / 8, digest_size_in_bytes, true, message) end, -- SHAKE256 + -- HMAC: + hmac = hmac, -- HMAC(hash_func, key, message) is applicable to any hash function from this module except SHAKE* and BLAKE* + -- misc utilities: + hex_to_bin = hex_to_bin, -- converts hexadecimal representation to binary string + bin_to_hex = bin_to_hex, -- converts binary string to hexadecimal representation + base64_to_bin = base64_to_bin, -- converts base64 representation to binary string + bin_to_base64 = bin_to_base64, -- converts binary string to base64 representation + -- old style names for backward compatibility: + hex2bin = hex_to_bin, + bin2hex = bin_to_hex, + base642bin = base64_to_bin, + bin2base64 = bin_to_base64, + -- BLAKE2 hash functions: + blake2b = blake2b, -- BLAKE2b (message, key, salt, digest_size_in_bytes) + blake2s = blake2s, -- BLAKE2s (message, key, salt, digest_size_in_bytes) + blake2bp = blake2bp, -- BLAKE2bp(message, key, salt, digest_size_in_bytes) + blake2sp = blake2sp, -- BLAKE2sp(message, key, salt, digest_size_in_bytes) + blake2xb = blake2xb, -- BLAKE2Xb(digest_size_in_bytes, message, key, salt) + blake2xs = blake2xs, -- BLAKE2Xs(digest_size_in_bytes, message, key, salt) + -- BLAKE2 aliases: + blake2 = blake2b, + blake2b_160 = function (message, key, salt) return blake2b(message, key, salt, 20) end, -- BLAKE2b-160 + blake2b_256 = function (message, key, salt) return blake2b(message, key, salt, 32) end, -- BLAKE2b-256 + blake2b_384 = function (message, key, salt) return blake2b(message, key, salt, 48) end, -- BLAKE2b-384 + blake2b_512 = blake2b, -- 64 -- BLAKE2b-512 + blake2s_128 = function (message, key, salt) return blake2s(message, key, salt, 16) end, -- BLAKE2s-128 + blake2s_160 = function (message, key, salt) return blake2s(message, key, salt, 20) end, -- BLAKE2s-160 + blake2s_224 = function (message, key, salt) return blake2s(message, key, salt, 28) end, -- BLAKE2s-224 + blake2s_256 = blake2s, -- 32 -- BLAKE2s-256 + -- BLAKE3 hash function + blake3 = blake3, -- BLAKE3 (message, key, digest_size_in_bytes) + blake3_derive_key = blake3_derive_key, -- BLAKE3_KDF(key_material, context_string, derived_key_size_in_bytes) +} + + +block_size_for_HMAC = { + [sha.md5] = 64, + [sha.sha1] = 64, + [sha.sha224] = 64, + [sha.sha256] = 64, + [sha.sha512_224] = 128, + [sha.sha512_256] = 128, + [sha.sha384] = 128, + [sha.sha512] = 128, + [sha.sha3_224] = 144, -- (1600 - 2 * 224) / 8 + [sha.sha3_256] = 136, -- (1600 - 2 * 256) / 8 + [sha.sha3_384] = 104, -- (1600 - 2 * 384) / 8 + [sha.sha3_512] = 72, -- (1600 - 2 * 512) / 8 +} + + +return sha diff --git a/runtime/lua/socket.lua b/runtime/lua/socket.lua new file mode 100644 index 0000000000..dec749aca5 --- /dev/null +++ b/runtime/lua/socket.lua @@ -0,0 +1,148 @@ +----------------------------------------------------------------------------- +-- LuaSocket helper module +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local string = require("string") +local math = require("math") +local socket = require("socket.core") + +local _M = socket + +----------------------------------------------------------------------------- +-- Exported auxiliar functions +----------------------------------------------------------------------------- +function _M.connect4(address, port, laddress, lport) + return socket.connect(address, port, laddress, lport, "inet") +end + +function _M.connect6(address, port, laddress, lport) + return socket.connect(address, port, laddress, lport, "inet6") +end + +function _M.bind(host, port, backlog) + if host == "*" then host = "0.0.0.0" end + local addrinfo, err = socket.dns.getaddrinfo(host); + if not addrinfo then return nil, err end + local sock, res + err = "no info on address" + for i, alt in base.ipairs(addrinfo) do + if alt.family == "inet" then + sock, err = socket.tcp4() + else + sock, err = socket.tcp6() + end + if not sock then return nil, err end + res, err = sock:bind(alt.addr, port) + if not res then + sock:close() + else + res, err = sock:listen(backlog) + if not res then + sock:close() + else + return sock + end + end + end + return nil, err +end + +_M.try = _M.newtry() + +function _M.choose(table) + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 + end + local f = table[name or "nil"] + if not f then base.error("unknown key (".. base.tostring(name) ..")", 3) + else return f(opt1, opt2) end + end +end + +----------------------------------------------------------------------------- +-- Socket sources and sinks, conforming to LTN12 +----------------------------------------------------------------------------- +-- create namespaces inside LuaSocket namespace +local sourcet, sinkt = {}, {} +_M.sourcet = sourcet +_M.sinkt = sinkt + +_M.BLOCKSIZE = 2048 + +sinkt["close-when-done"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then + sock:close() + return 1 + else return sock:send(chunk) end + end + }) +end + +sinkt["keep-open"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if chunk then return sock:send(chunk) + else return 1 end + end + }) +end + +sinkt["default"] = sinkt["keep-open"] + +_M.sink = _M.choose(sinkt) + +sourcet["by-length"] = function(sock, length) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if length <= 0 then return nil end + local size = math.min(socket.BLOCKSIZE, length) + local chunk, err = sock:receive(size) + if err then return nil, err end + length = length - string.len(chunk) + return chunk + end + }) +end + +sourcet["until-closed"] = function(sock) + local done + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if done then return nil end + local chunk, err, partial = sock:receive(socket.BLOCKSIZE) + if not err then return chunk + elseif err == "closed" then + sock:close() + done = 1 + return partial + else return nil, err end + end + }) +end + + +sourcet["default"] = sourcet["until-closed"] + +_M.source = _M.choose(sourcet) + +return _M diff --git a/spec/System/TestImportReimport_spec.lua b/spec/System/TestImportReimport_spec.lua index c75bfdfc8a..f293eb0ece 100644 --- a/spec/System/TestImportReimport_spec.lua +++ b/spec/System/TestImportReimport_spec.lua @@ -50,10 +50,10 @@ describe("TestImportReimport", function() -- Build a minimal import payload so the tests stay focused on state, not fixture noise. local function buildImportPayload(items) - return dkjson.encode({ - character = { level = DEFAULT_CHARACTER_LEVEL }, - items = items, - }) + return { + level = DEFAULT_CHARACTER_LEVEL, + equipment = items, + } end local function reimportSocketedItemsWithOptions(itemTypeLine, inventoryId, socketedItems, clearItems) diff --git a/spec/System/TestTradeQueryCurrency_spec.lua b/spec/System/TestTradeQueryCurrency_spec.lua index d412af2950..7dcdeb8a18 100644 --- a/spec/System/TestTradeQueryCurrency_spec.lua +++ b/spec/System/TestTradeQueryCurrency_spec.lua @@ -5,40 +5,20 @@ describe("TradeQuery Currency Conversion", function() mock_tradeQuery = new("TradeQuery", { itemsTab = {} }) end) - -- test case for commit: "Skip callback on errors to prevent incomplete conversions" - describe("FetchCurrencyConversionTable", function() - -- Pass: Callback not called on error - -- Fail: Callback called, indicating partial data risk - it("skips callback on error", function() - local orig_launch = launch - local spy = { called = false } - launch = { - DownloadPage = function(url, callback, opts) - callback(nil, "test error") - end - } - mock_tradeQuery:FetchCurrencyConversionTable(function() - spy.called = true - end) - launch = orig_launch - assert.is_false(spy.called) - end) - end) - - describe("ConvertCurrencyToChaos", function() - -- Pass: Ceils amount to integer (e.g., 4.9 -> 5) - -- Fail: Wrong value or nil, indicating broken rounding/baseline logic, causing inaccurate chaos totals + describe("ConvertCurrencyToDivs", function() + -- Pass: Calculates price in divs + -- Fail: Wrong value or nil, indicating broken rounding/baseline logic it("handles chaos currency", function() - mock_tradeQuery.pbCurrencyConversion = { league = { chaos = 1 } } + mock_tradeQuery.pbCurrencyConversion = { league = { chaos = 0.1 } } mock_tradeQuery.pbLeague = "league" - local result = mock_tradeQuery:ConvertCurrencyToChaos("chaos", 4.9) - assert.are.equal(result, 5) + local result = mock_tradeQuery:ConvertCurrencyToDivs("chaos", 5) + assert.are.equal(result, 0.5) end) -- Pass: Returns nil without crash -- Fail: Crashes or wrong value, indicating unhandled currencies, corrupting price conversions it("returns nil for unmapped", function() - local result = mock_tradeQuery:ConvertCurrencyToChaos("exotic", 10) + local result = mock_tradeQuery:ConvertCurrencyToDivs("exotic", 10) assert.is_nil(result) end) end) @@ -46,19 +26,35 @@ describe("TradeQuery Currency Conversion", function() describe("PriceBuilderProcessPoENinjaResponse", function() -- Pass: Processes without error, restoring map while adding a notice -- Fail: Corrupts map or crashes, indicating fragile API response handling, breaking future conversions - it("handles unmapped currency", function() + it("handles empty response", function() local orig_conv = mock_tradeQuery.currencyConversionTradeMap mock_tradeQuery.currencyConversionTradeMap = { div = "id" } mock_tradeQuery.pbLeague = "league" mock_tradeQuery.pbCurrencyConversion = { league = {} } - mock_tradeQuery.controls.pbNotice = { label = ""} - local resp = { exotic = 10 } - mock_tradeQuery:PriceBuilderProcessPoENinjaResponse(resp) + mock_tradeQuery.controls.pbNotice = { label = "" } + local resp = { lines = { }} + mock_tradeQuery:PriceBuilderProcessPoENinjaResponse(resp.lines) -- No crash expected assert.is_true(true) assert.is_true(mock_tradeQuery.controls.pbNotice.label == "No currencies received from PoE Ninja") mock_tradeQuery.currencyConversionTradeMap = orig_conv end) + + -- Pass: Processes without error, restoring map while adding a notice + -- Fail: Corrupts map or crashes, indicating fragile API response handling, breaking future conversions + it("handles empty response", function() + local orig_conv = mock_tradeQuery.currencyConversionTradeMap + mock_tradeQuery.currencyConversionTradeMap = { div = "id" } + mock_tradeQuery.pbLeague = "league" + mock_tradeQuery.pbCurrencyConversion = { league = {} } + mock_tradeQuery.controls.pbNotice = { label = "" } + local resp = { lines = { { malformedLine = "lol"} }} + mock_tradeQuery:PriceBuilderProcessPoENinjaResponse(resp.lines) + -- No crash expected + assert.is_true(true) + assert.is_true(mock_tradeQuery.controls.pbNotice.label == "Currencies not updated: malformed PoE Ninja response") + mock_tradeQuery.currencyConversionTradeMap = orig_conv + end) end) describe("GetTotalPriceString", function() diff --git a/spec/System/TestTradeQueryRequests_spec.lua b/spec/System/TestTradeQueryRequests_spec.lua index 873b5102d5..0e731701f8 100644 --- a/spec/System/TestTradeQueryRequests_spec.lua +++ b/spec/System/TestTradeQueryRequests_spec.lua @@ -65,6 +65,42 @@ describe("TradeQueryRequests", function() launch = orig_launch end) + -- Pass: Does not crash on 401, and passes error message + -- Fail: Crash, or returned error is wrong + it("does not crash on 401", function() + local json = '"{"error":"invalid_token","error_description":"The access token provided is invalid or has expired"}"' + local header = [[HTTP/1.1 401 Unauthorized +Date: Fri, 24 Apr 2026 07:30:38 GMT +Content-Type: application/json +Transfer-Encoding: chunked +Connection: keep-alive +Server: cloudflare +WWW-Authenticate: Bearer realm="pathofexile:production", error="invalid_token", error_description="The access token provided is invalid or has expired" +Cache-Control: no-store +Strict-Transport-Security: max-age=63115200; includeSubDomains; preload]] + local orig_launch = launch + launch = { + DownloadPage = function(url, onComplete, opts) + onComplete({ body = json, header = header }, nil) + end + } + table.insert(requests.requestQueue.search, { + url = "test", + callback = function(body, msg) + assert.are.equal(body, json) + assert.are.equal(msg, "Response code: 401\nAuthorization is invalid. Please Re-Log and reset") + end, + retryTime = nil + }) + local function mock_next_time(self, policy, time) + return time - 1 + end + mock_limiter.NextRequestTime = mock_next_time + requests:ProcessQueue() + assert.are.equal(#requests.requestQueue.search, 0) + launch = orig_launch + end) + -- Pass: Retries with increasing backoff up to cap, preventing infinite loops -- Fail: No backoff or uncapped, indicating retry bug, risking API bans it("retries on 429 with exponential backoff", function() @@ -192,4 +228,4 @@ describe("TradeQueryRequests", function() requests.FetchResultBlock = orig_fetchBlock end) end) -end) \ No newline at end of file +end) diff --git a/src/Classes/ImportTab.lua b/src/Classes/ImportTab.lua index 8d6a824f98..3ab95b544f 100644 --- a/src/Classes/ImportTab.lua +++ b/src/Classes/ImportTab.lua @@ -8,6 +8,8 @@ local t_insert = table.insert local t_remove = table.remove local b_rshift = bit.rshift local band = bit.band +local m_max = math.max +local dkjson = require "dkjson" local realmList = { { label = "PC", id = "PC", realmCode = "pc", hostName = "https://www.pathofexile.com/", profileURL = "account/view-profile/" }, @@ -19,28 +21,237 @@ local realmList = { local influenceInfo = itemLib.influenceInfo.all -local ImportTabClass = newClass("ImportTab", "ControlHost", "Control", function(self, build) - self.ControlHost() - self.Control() +function addOAuthControls(self) + self.usingOauth = true + self.isAuthorized = function() return main.api.authToken ~= nil end + -- either the 30 second timer for oauth, or a rate limit timer + --- @type integer? + self.oauthTimer = nil + --- @type string? + self.oauthErrCode = nil + --- @type bool + self.oauthLoading = false + -- an array of Character for each realm. Note this is for the character + -- list, which will mean that equipment and passives are missing + -- https://www.pathofexile.com/developer/docs/reference#type-Character + --- @type table + self.characterList = {} + function charImportStatus() + if not self.isAuthorized() and not self.oauthTimer then + return colorCodes.WARNING .. "Not authenticated" .. (self.oauthErrCode or "") + elseif not self.isAuthorized() and self.oauthTimer then + local timeLeft = m_max(0, (self.oauthTimer + 30) - os.time()) + if timeLeft < 1 then + self.oauthTimer = nil + return colorCodes.WARNING .. "Not authenticated" .. (self.oauthErrCode or "") + end + return string.format("Logging in... (%d)", timeLeft) .. (self.oauthErrCode or "") + -- user is spam changing realms and is rate limited + elseif self.isAuthorized() and self.oauthTimer then + local timeLeft = m_max(0, (self.oauthTimer + 30) - os.time()) + if timeLeft < 1 then + self.oauthTimer = nil + return "Authenticated" + end + return colorCodes.WARNING .. string.format("You're doing that too fast. Please wait (%d)", timeLeft) + elseif self.isAuthorized() and self.oauthLoading then + return "Fetching..." + elseif self.isAuthorized() then + return "Authenticated" + end + -- unreachable + return "" + end - self.build = build + -- space after labels + local labelSpacing = 6 + -- space between rows + local rowSpacing = 6 + + + self.controls.charImportStatusLabel = new("LabelControl", { "TOPLEFT", self.controls.sectionOauthCharImport, "TOPLEFT" }, + { labelSpacing, 14, 200, 16 }, function() + return "^7Character import status: " .. charImportStatus() + end) + + self.controls.logoutApiButton = new("ButtonControl", { "TOPLEFT", self.controls.charImportStatusLabel, "TOPRIGHT" }, + { labelSpacing, 0, 170, 16 }, "^7Logout from Path of Exile API", function() + main.api:ResetDetails() + main:SaveSettings() + end) + self.controls.logoutApiButton.shown = function() return self.usingOauth and self.isAuthorized() end + + self.controls.characterImportAnchor = new("Control", { "TOPLEFT", self.controls.sectionOauthCharImport, "TOPLEFT" }, + { labelSpacing, 40, 200, 16 }) + self.controls.sectionOauthCharImport.height = function() + return self.isAuthorized() and 200 or 60 + end + + -- OAuth Stage: Authenticate + self.controls.authenticateButton = new("ButtonControl", { "TOPLEFT", self.controls.characterImportAnchor, "TOPLEFT" }, + { 0, 0, 200, 16 }, "^7Authorize with Path of Exile", function() + main.api:FetchAuthToken(function(errCode) + if errCode then + self.oauthErrCode = errCode + self.oauthTimer = nil + else + self.oauthErrCode = nil + ConPrintf("%s", main.api.authToken) + self.oauthTimer = nil + end + end) + self.oauthTimer = os.time() + end) + self.controls.authenticateButton.shown = function() + return self.usingOauth and not self.isAuthorized() + end + + -- Stage: select realm, league, character, and import data + self.controls.charSelectHeader = new("LabelControl", { "TOPLEFT", self.controls.sectionOauthCharImport, "TOPLEFT" }, + { labelSpacing, 40, 200, 16 }, "^7Choose character to import data from:") + self.controls.charSelectHeader.shown = function() + return self.usingOauth and self.isAuthorized() + end + + -- realm select + function setLeaguesFromCharList() + local currentRealm = self.controls.accountRealm:GetSelValue().realmCode + local currentCharacters = currentRealm and self.characterList[currentRealm] + if not currentCharacters or #currentCharacters == 0 then + self.controls.charSelectLeague:SetList({}) + self.controls.charSelect:SetList({}) + return + end + local set = {} + + for _, character in ipairs(currentCharacters) do + -- the api reference says league is (somehow) not necessarily present + if character.league then + set[character.league] = true + end + end + local ret = {"Any"} + -- TODO sort these + for key, _ in pairs(set) do + t_insert(ret, key) + end + self.controls.charSelectLeague:SetList(ret) + -- TODO prefer last + self.controls.charSelectLeague.selIndex = nil + self.controls.charSelectLeague:SetSel(1) + end + + function fetchCharacters() + local realm = self.controls.accountRealm:GetSelValue() + self.oauthLoading = true + function onResponse(body, err, timeNext) + if not err then + self.characterList[realm.realmCode] = body.characters + setLeaguesFromCharList() + self.oauthLoading = false + return + elseif err == "Response code: 429" then + self.oauthTimer = timeNext + else + self.oauthErrCode = err + end + self.oauthLoading = false + end + main.api:DownloadCharacterList(realm.realmCode, onResponse) + end + self.controls.accountRealm = new("DropDownControl", { "TOPLEFT", self.controls.charSelectHeader, "BOTTOMLEFT" }, + { 0, rowSpacing, 60, 20 }, realmList, function() + setLeaguesFromCharList() + end) + self.controls.accountRealm:SelByValue(main.lastRealm or "PC", "id") + self.controls.accountRealmFetchButton = new("ButtonControl", {"LEFT", self.controls.accountRealm, "RIGHT"}, {labelSpacing, 0, 130, 20}, "Fetch Characters", fetchCharacters) + + -- league select + --- @param newLeague string + function onLeagueChange(_, newLeague) + local realm = self.controls.accountRealm:GetSelValue().realmCode + if newLeague == "Any" then + self:BuildCharacterList(realm, nil, self.characterList[realm], self.controls.charSelect) + else + self:BuildCharacterList(realm, newLeague, self.characterList[realm], self.controls.charSelect) + end + end + + self.controls.charSelectLeagueLabel = new("LabelControl", { "TOPLEFT", self.controls.accountRealm, "BOTTOMLEFT" }, + { 0, rowSpacing, 0, 14 }, "^7League:") + self.controls.charSelectLeague = new("DropDownControl", { "LEFT", self.controls.charSelectLeagueLabel, "RIGHT" }, + { labelSpacing, 0, 150, 18 }, nil, onLeagueChange) + -- character select + self.controls.charSelect = new("DropDownControl", { "TOPLEFT", self.controls.charSelectLeagueLabel, "BOTTOMLEFT" }, + { 0, rowSpacing, 400, 18 }, nil) + self.controls.charSelect.enabled = function() + return self.usingOauth and self.isAuthorized() + end + + -- import action controls + self.controls.charImportHeader = new("LabelControl", { "TOPLEFT", self.controls.charSelect, "BOTTOMLEFT" }, + { 0, rowSpacing, 200, 16 }, "^7Import:") + self.controls.charImportTree = new("ButtonControl", { "LEFT", self.controls.charImportHeader, "RIGHT" }, + { labelSpacing, 0, 170, 20 }, "Passive Tree and Jewels", function() + local realm = self.controls.accountRealm:GetSelValue().realmCode + local selectedName = self.controls.charSelect:GetSelValue().label + if self.build.spec:CountAllocNodes() > 0 then + main:OpenConfirmPopup("Character Import", "Importing the passive tree will overwrite your current tree.", + "Import", function() + main.api:DownloadCharacter(realm, selectedName, function(char) + self:ImportPassiveTreeAndJewels(char.character) + end) + end) + else + main.api:DownloadCharacter(realm, selectedName, function(char) + self:ImportPassiveTreeAndJewels(char.character) + end) + end + end) + self.controls.charImportTree.enabled = function() + return self.usingOauth and self.isAuthorized() and self.controls.charSelect:GetSelValue() + end + self.controls.charImportTreeClearJewels = new("CheckBoxControl", { "LEFT", self.controls.charImportTree, "RIGHT" }, + { 90, 0, 18 }, "Delete jewels:", nil, "Delete all existing jewels when importing.", true) + self.controls.charImportItems = new("ButtonControl", { "TOPLEFT", self.controls.charImportTree, "BOTTOMLEFT" }, + { 0, rowSpacing, 110, 20 }, "Items and Skills", function() + local realm = self.controls.accountRealm:GetSelValue().realmCode + local selectedName = self.controls.charSelect:GetSelValue().label + main.api:DownloadCharacter(realm, selectedName, function(char) + self:ImportItemsAndSkills(char.character) + end) + end) + self.controls.charImportItems.enabled = function() + return self.usingOauth and self.isAuthorized() and self.controls.charSelect:GetSelValue() + end + self.controls.charImportItemsClearSkills = new("CheckBoxControl", { "LEFT", self.controls.charImportItems, "RIGHT" }, + { 85, 0, 18 }, "Delete skills:", nil, "Delete all existing skills when importing.", true) + self.controls.charImportItemsClearItems = new("CheckBoxControl", { "LEFT", self.controls.charImportItems, "RIGHT" }, + { 220, 0, 18 }, "Delete equipment:", nil, "Delete all equipped items when importing.", true) + self.controls.charImportItemsIgnoreWeaponSwap = new("CheckBoxControl", { "LEFT", self.controls.charImportItems, + "RIGHT" }, { 380, 0, 18 }, "Ignore weapon swap:", nil, "Ignore items and skills in weapon swap.", false) +end +function addAccountNameControls(self) self.charImportMode = "GETACCOUNTNAME" self.charImportStatus = "Idle" - self.controls.sectionCharImport = new("SectionControl", {"TOPLEFT",self,"TOPLEFT"}, {10, 18, 650, 250}, "Character Import") - self.controls.charImportStatusLabel = new("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, {6, 14, 200, 16}, function() - return "^7Character import status: "..self.charImportStatus + self.controls.siteCharImportStatusLabel = new("LabelControl", { "TOPLEFT", self.controls.sectionCharSiteImport, "TOPLEFT" }, + { 6, 14, 200, 16 }, function() + return "^7Character import status: " .. self.charImportStatus end) -- Stage: input account name - self.controls.accountNameHeader = new("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, {6, 40, 200, 16}, "^7To start importing a character, enter the character's account name:") - self.controls.accountNameHeader.shown = function() + self.controls.siteAccountNameHeader = new("LabelControl", { "TOPLEFT", self.controls.sectionCharSiteImport, "TOPLEFT" }, + { 6, 40, 250, 16 }, "^7To start importing a character, enter the character's account name:") + self.controls.siteAccountNameHeader.shown = function() return self.charImportMode == "GETACCOUNTNAME" end - self.controls.accountRealm = new("DropDownControl", {"TOPLEFT",self.controls.accountNameHeader,"BOTTOMLEFT"}, {0, 4, 60, 20}, realmList) - self.controls.accountRealm:SelByValue(main.lastRealm or "PC", "id") - self.controls.accountName = new("EditControl", {"LEFT",self.controls.accountRealm,"RIGHT"}, {8, 0, 200, 20}, main.lastAccountName or "", nil, "%c", nil, nil, nil, nil, true) - self.controls.accountName.pasteFilter = function(text) + self.controls.siteAccountRealm = new("DropDownControl", { "TOPLEFT", self.controls.siteAccountNameHeader, "BOTTOMLEFT" }, + { 0, 4, 60, 20 }, realmList) + self.controls.siteAccountRealm:SelByValue(main.lastRealm or "PC", "id") + self.controls.siteAccountName = new("EditControl", { "LEFT", self.controls.siteAccountRealm, "RIGHT" }, { 8, 0, 200, 20 }, + main.lastAccountName or "", nil, "%c", nil, nil, nil, nil, true) + self.controls.siteAccountName.pasteFilter = function(text) return text:gsub(".", function(c) local byte = c:byte() if byte >= 128 then @@ -52,109 +263,85 @@ local ImportTabClass = newClass("ImportTab", "ControlHost", "Control", function( end -- accountHistory Control if not historyList then - historyList = { } + historyList = {} for accountName, account in pairs(main.gameAccounts) do t_insert(historyList, accountName) historyList[accountName] = true end - table.sort(historyList, function(a,b) + table.sort(historyList, function(a, b) return a:lower() < b:lower() end) end -- don't load the list many times - self.controls.accountNameGo = new("ButtonControl", {"LEFT",self.controls.accountName,"RIGHT"}, {8, 0, 60, 20}, "Start", function() - self.controls.sessionInput.buf = "" - self:DownloadCharacterList() + self.controls.siteAccountNameGo = new("ButtonControl", { "LEFT", self.controls.siteAccountName, "RIGHT" }, { 8, 0, 60, 20 }, + "Start", function() + self:DownloadSiteCharacterList() end) - self.controls.accountNameGo.enabled = function() - return self.controls.accountName.buf:match("%S[#%-]%d%d%d%d$") + self.controls.siteAccountNameGo.enabled = function() + return self.controls.siteAccountName.buf:match("%S[#%-]%d%d%d%d$") end - self.controls.accountNameGo.tooltipFunc = function(tooltip) + self.controls.siteAccountNameGo.tooltipFunc = function(tooltip) tooltip:Clear() - if not self.controls.accountName.buf:match("[#%-]%d%d%d%d$") and self.controls.accountName.buf ~= "" then - tooltip:AddLine(16, "^7Missing discriminator e.g. " .. self.controls.accountName.buf .. "#1234") + if not self.controls.siteAccountName.buf:match("[#%-]%d%d%d%d$") and self.controls.siteAccountName.buf ~= "" then + tooltip:AddLine(16, "^7Missing discriminator e.g. " .. self.controls.siteAccountName.buf .. "#1234") end end - self.controls.accountHistory = new("DropDownControl", {"LEFT",self.controls.accountNameGo,"RIGHT"}, {8, 0, 200, 20}, historyList, function() - self.controls.accountName.buf = self.controls.accountHistory.list[self.controls.accountHistory.selIndex] + self.controls.siteAccountHistory = new("DropDownControl", { "LEFT", self.controls.siteAccountNameGo, "RIGHT" }, + { 8, 0, 200, 20 }, historyList, function() + self.controls.siteAccountName.buf = self.controls.siteAccountHistory.list[self.controls.siteAccountHistory.selIndex] end) - self.controls.accountHistory:SelByValue(main.lastAccountName) - self.controls.accountHistory:CheckDroppedWidth(true) + self.controls.siteAccountHistory:SelByValue(main.lastAccountName) + self.controls.siteAccountHistory:CheckDroppedWidth(true) - self.controls.removeAccount = new("ButtonControl", {"LEFT",self.controls.accountHistory,"RIGHT"}, {8, 0, 20, 20}, "X", function() - local accountName = self.controls.accountHistory.list[self.controls.accountHistory.selIndex] + self.controls.siteRemoveAccount = new("ButtonControl", { "LEFT", self.controls.siteAccountHistory, "RIGHT" }, { 8, 0, 20, 20 }, + "X", function() + local accountName = self.controls.siteAccountHistory.list[self.controls.siteAccountHistory.selIndex] if (accountName ~= nil) then - t_remove(self.controls.accountHistory.list, self.controls.accountHistory.selIndex) - self.controls.accountHistory.list[accountName] = nil - main.gameAccounts[accountName] = nil + t_remove(self.controls.siteAccountHistory.list, self.controls.siteAccountHistory.selIndex) + self.controls.siteAccountHistory.list[accountName] = nil + main.gameAccounts[accountName] = nil end end) - self.controls.removeAccount.tooltipFunc = function(tooltip) + self.controls.siteRemoveAccount.tooltipFunc = function(tooltip) tooltip:Clear() tooltip:AddLine(16, "^7Removes account from the dropdown list") end - self.controls.accountNameMissingDiscriminator = new("LabelControl", {"TOPLEFT",self.controls.accountName,"BOTTOMLEFT"}, {0, 8, 0, 16}, "^1Missing discriminator e.g. #1234") - self.controls.accountNameMissingDiscriminator.shown = function() - return not self.controls.accountName.buf:match("[#%-]%d%d%d%d$") and self.controls.accountName.buf ~= "" + self.controls.siteAccountNameMissingDiscriminator = new("LabelControl", + { "TOPLEFT", self.controls.siteAccountName, "BOTTOMLEFT" }, { 0, 8, 0, 16 }, "^1Missing discriminator e.g. #1234") + self.controls.siteAccountNameMissingDiscriminator.shown = function() + return not self.controls.siteAccountName.buf:match("[#%-]%d%d%d%d$") and self.controls.siteAccountName.buf ~= "" end - self.controls.accountNameUnicode = new("LabelControl", {"TOPLEFT",self.controls.accountRealm,"BOTTOMLEFT"}, {0, 34, 0, 14}, "^7Note: if the account name contains non-ASCII characters it must be pasted into the textbox,\nnot typed manually.") - - -- Stage: input POESESSID - self.controls.sessionHeader = new("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, {6, 40, 200, 14}) - self.controls.sessionHeader.label = function() - return [[ -^7The list of characters on ']]..self.controls.accountName.buf..[[' couldn't be retrieved. This may be because: -1. You are missing the discriminator at the end of the account name e.g. #1234 -2. You entered a character name instead of an account name or -3. This account's characters tab is hidden (this is the default setting). -If this is your account, you can either: -1. Uncheck "Hide Characters" in your privacy settings or -2. Enter a POESESSID below. -You can get this from your web browser's cookies while logged into the Path of Exile website. - ]] - end - self.controls.sessionHeader.shown = function() - return self.charImportMode == "GETSESSIONID" - end - self.controls.sessionRetry = new("ButtonControl", {"TOPLEFT",self.controls.sessionHeader,"TOPLEFT"}, {0, 122, 60, 20}, "Retry", function() - self:DownloadCharacterList() - end) - self.controls.sessionCancel = new("ButtonControl", {"LEFT",self.controls.sessionRetry,"RIGHT"}, {8, 0, 60, 20}, "Cancel", function() - self.charImportMode = "GETACCOUNTNAME" - self.charImportStatus = "Idle" - end) - self.controls.sessionPrivacySettings = new("ButtonControl", {"LEFT",self.controls.sessionCancel,"RIGHT"}, {8, 0, 120, 20}, "Privacy Settings", function() - OpenURL('https://www.pathofexile.com/my-account/privacy') - end) - self.controls.sessionInput = new("EditControl", {"TOPLEFT",self.controls.sessionRetry,"BOTTOMLEFT"}, {0, 8, 350, 20}, "", "POESESSID", "%X", 32) - self.controls.sessionInput:SetProtected(true) - self.controls.sessionGo = new("ButtonControl", {"LEFT",self.controls.sessionInput,"RIGHT"}, {8, 0, 60, 20}, "Go", function() - self:DownloadCharacterList() - end) - self.controls.sessionGo.enabled = function() - return #self.controls.sessionInput.buf == 32 - end + self.controls.siteAccountNameUnicode = new("LabelControl", { "TOPLEFT", self.controls.siteAccountRealm, "BOTTOMLEFT" }, + { 0, 34, 0, 14 }, + "^7Note: if the account name contains non-ASCII characters it must be pasted into the textbox,\nnot typed manually.") -- Stage: select character and import data - self.controls.charSelectHeader = new("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, {6, 40, 200, 16}, "^7Choose character to import data from:") - self.controls.charSelectHeader.shown = function() + self.controls.siteCharSelectHeader = new("LabelControl", { "TOPLEFT", self.controls.sectionCharSiteImport, "TOPLEFT" }, + { 6, 40, 200, 16 }, "^7Choose character to import data from:") + self.controls.siteCharSelectHeader.shown = function() return self.charImportMode == "SELECTCHAR" or self.charImportMode == "IMPORTING" end - self.controls.charSelectLeagueLabel = new("LabelControl", {"TOPLEFT",self.controls.charSelectHeader,"BOTTOMLEFT"}, {0, 6, 0, 14}, "^7League:") - self.controls.charSelectLeague = new("DropDownControl", {"LEFT",self.controls.charSelectLeagueLabel,"RIGHT"}, {4, 0, 150, 18}, nil, function(index, value) - self:BuildCharacterList(value.league) - end) - self.controls.charSelect = new("DropDownControl", {"TOPLEFT",self.controls.charSelectHeader,"BOTTOMLEFT"}, {0, 24, 400, 18}) - self.controls.charSelect.enabled = function() + self.controls.siteCharSelectLeagueLabel = new("LabelControl", { "TOPLEFT", self.controls.siteCharSelectHeader, "BOTTOMLEFT" }, + { 0, 6, 0, 14 }, "^7League:") + self.controls.siteCharSelectLeague = new("DropDownControl", { "LEFT", self.controls.siteCharSelectLeagueLabel, "RIGHT" }, + { 4, 0, 150, 18 }, nil, function(index, value) + self:BuildCharacterList("pc", value.league, self.lastCharList, self.controls.siteCharSelect) + end) + self.controls.siteCharSelect = new("DropDownControl", { "TOPLEFT", self.controls.siteCharSelectHeader, "BOTTOMLEFT" }, + { 0, 24, 400, 18 }) + self.controls.siteCharSelect.enabled = function() return self.charImportMode == "SELECTCHAR" end - self.controls.charImportHeader = new("LabelControl", {"TOPLEFT",self.controls.charSelect,"BOTTOMLEFT"}, {0, 16, 200, 16}, "^7Import:") - self.controls.charImportTree = new("ButtonControl", {"LEFT",self.controls.charImportHeader, "RIGHT"}, {8, 0, 170, 20}, "Passive Tree and Jewels", function() + self.controls.siteCharImportHeader = new("LabelControl", { "TOPLEFT", self.controls.siteCharSelect, "BOTTOMLEFT" }, + { 0, 16, 200, 16 }, "^7Import:") + self.controls.siteCharImportTree = new("ButtonControl", { "LEFT", self.controls.siteCharImportHeader, "RIGHT" }, + { 8, 0, 170, 20 }, "Passive Tree and Jewels", function() if self.build.spec:CountAllocNodes() > 0 then - main:OpenConfirmPopup("Character Import", "Importing the passive tree will overwrite your current tree.", "Import", function() + main:OpenConfirmPopup("Character Import", "Importing the passive tree will overwrite your current tree.", + "Import", function() self:DownloadPassiveTree() end) else @@ -162,30 +349,65 @@ You can get this from your web browser's cookies while logged into the Path of E end self:SetPredefinedBuildName() end) - self.controls.charImportTree.enabled = function() + self.controls.siteCharImportTree.enabled = function() return self.charImportMode == "SELECTCHAR" end - self.controls.charImportTreeClearJewels = new("CheckBoxControl", {"LEFT",self.controls.charImportTree,"RIGHT"}, {90, 0, 18}, "Delete jewels:", nil, "Delete all existing jewels when importing.", true) - self.controls.charImportItems = new("ButtonControl", {"LEFT",self.controls.charImportTree, "LEFT"}, {0, 36, 110, 20}, "Items and Skills", function() + self.controls.siteCharImportTreeClearJewels = new("CheckBoxControl", { "LEFT", self.controls.siteCharImportTree, "RIGHT" }, + { 90, 0, 18 }, "Delete jewels:", nil, "Delete all existing jewels when importing.", true) + self.controls.siteCharImportItems = new("ButtonControl", { "LEFT", self.controls.siteCharImportTree, "LEFT" }, + { 0, 36, 110, 20 }, "Items and Skills", function() self:DownloadItems() self:SetPredefinedBuildName() end) - self.controls.charImportItems.enabled = function() + self.controls.siteCharImportItems.enabled = function() return self.charImportMode == "SELECTCHAR" end - self.controls.charImportItemsClearSkills = new("CheckBoxControl", {"LEFT",self.controls.charImportItems,"RIGHT"}, {85, 0, 18}, "Delete skills:", nil, "Delete all existing skills when importing.", true) - self.controls.charImportItemsClearItems = new("CheckBoxControl", {"LEFT",self.controls.charImportItems,"RIGHT"}, {220, 0, 18}, "Delete equipment:", nil, "Delete all equipped items when importing.", true) - self.controls.charImportItemsIgnoreWeaponSwap = new("CheckBoxControl", {"LEFT",self.controls.charImportItems,"RIGHT"}, {380, 0, 18}, "Ignore weapon swap:", nil, "Ignore items and skills in weapon swap.", false) - self.controls.charBanditNote = new("LabelControl", {"TOPLEFT",self.controls.charImportHeader,"BOTTOMLEFT"}, {0, 50, 200, 14}, "^7Tip: After you finish importing a character, make sure you update the bandit choice,\nas it cannot be imported.") - - self.controls.charClose = new("ButtonControl", {"TOPLEFT",self.controls.charImportHeader,"BOTTOMLEFT"}, {0, 90, 60, 20}, "Close", function() + self.controls.siteCharImportItemsClearSkills = new("CheckBoxControl", { "LEFT", self.controls.siteCharImportItems, "RIGHT" }, + { 85, 0, 18 }, "Delete skills:", nil, "Delete all existing skills when importing.", true) + self.controls.siteCharImportItemsClearItems = new("CheckBoxControl", { "LEFT", self.controls.siteCharImportItems, "RIGHT" }, + { 220, 0, 18 }, "Delete equipment:", nil, "Delete all equipped items when importing.", true) + self.controls.siteCharImportItemsIgnoreWeaponSwap = new("CheckBoxControl", { "LEFT", self.controls.siteCharImportItems, + "RIGHT" }, { 380, 0, 18 }, "Ignore weapon swap:", nil, "Ignore items and skills in weapon swap.", false) + self.controls.siteCharBanditNote = new("LabelControl", { "TOPLEFT", self.controls.siteCharImportHeader, "BOTTOMLEFT" }, + { 0, 50, 200, 14 }, + "^7Tip: After you finish importing a character, make sure you update the bandit choice,\nas it can only be imported by logging in above.") + + self.controls.siteCharClose = new("ButtonControl", { "TOPLEFT", self.controls.siteCharImportHeader, "BOTTOMLEFT" }, + { 0, 90, 60, 20 }, "Close", function() self.charImportMode = "GETACCOUNTNAME" self.charImportStatus = "Idle" end) +end + +local ImportTabClass = newClass("ImportTab", "ControlHost", "Control", function(self, build) + self.ControlHost() + self.Control() + + self.build = build + + if not main.api then + main.api = new("PoEAPI", main.lastToken, main.lastRefreshToken, main.tokenExpiry) + end + + + self.controls.sectionOauthCharImport = new("SectionControl", { "TOPLEFT", self, "TOPLEFT" }, { 10, 18, 650, 200 }, + "Import From Your Account") + + addOAuthControls(self) + + self.controls.sectionCharSiteImport = new("SectionControl", + { "TOPLEFT", self.controls.sectionOauthCharImport, "BOTTOMLEFT" }, + { 0, 18, 650, 250 }, + "Import By Account Name") + addAccountNameControls(self) + -- Build import/export - self.controls.sectionBuild = new("SectionControl", {"TOPLEFT",self.controls.sectionCharImport,"BOTTOMLEFT"}, {0, 18, 650, 182}, "Build Sharing") - self.controls.generateCodeLabel = new("LabelControl", {"TOPLEFT",self.controls.sectionBuild,"TOPLEFT"}, {6, 14, 0, 16}, "^7Generate a code to share this build with other Path of Building users:") + self.controls.sectionBuild = new("SectionControl", + { "TOPLEFT", self.controls.sectionCharSiteImport, "BOTTOMLEFT", true }, + { 0, 18, 650, 182 }, "Build Sharing") + self.controls.generateCodeLabel = new("LabelControl", { "TOPLEFT", self.controls.sectionBuild, "TOPLEFT" }, + { 6, 14, 0, 16 }, "^7Generate a code to share this build with other Path of Building users:") self.controls.generateCode = new("ButtonControl", {"LEFT",self.controls.generateCodeLabel,"RIGHT"}, {4, 0, 80, 20}, "Generate", function() self.controls.generateCodeOut:SetText(common.base64.encode(Deflate(self.build:SaveDB("code"))):gsub("+","-"):gsub("/","_")) end) @@ -218,10 +440,10 @@ You can get this from your web browser's cookies while logged into the Path of E local exportWebsitesList = getExportSitesFromImportList() self.controls.exportFrom = new("DropDownControl", { "LEFT", self.controls.generateCodeCopy,"RIGHT"}, {8, 0, 120, 20}, exportWebsitesList, function(_, selectedWebsite) - main.lastExportedWebsite = selectedWebsite.id + main.lastExportWebsite = selectedWebsite.id self.exportWebsiteSelected = selectedWebsite.id end) - self.controls.exportFrom:SelByValue(self.exportWebsiteSelected or main.lastExportedWebsite or "Maxroll", "id") + self.controls.exportFrom:SelByValue(self.exportWebsiteSelected or main.lastExportWebsite or "Pastebin", "id") self.controls.generateCodeByLink = new("ButtonControl", { "LEFT", self.controls.exportFrom, "RIGHT"}, {8, 0, 100, 20}, "Share", function() local exportWebsite = exportWebsitesList[self.controls.exportFrom.selIndex] local subScriptId = buildSites.UploadBuild(self.controls.generateCodeOut.buf, exportWebsite) @@ -262,6 +484,7 @@ You can get this from your web browser's cookies while logged into the Path of E self.importCodeDetail = "" self.importCodeXML = nil self.importCodeValid = false + self.importCodeJson = nil if #buf == 0 then return @@ -291,6 +514,28 @@ You can get this from your web browser's cookies while logged into the Path of E end end + -- If we are in dev mode and the string is a json + if launch.devMode and urlText:match("^%{.*%}$") ~= nil then + local jsonData, _, errDecode = dkjson.decode(urlText) + if errDecode then + self.importCodeDetail = colorCodes.NEGATIVE.."Invalid JSON format (decode error)" + return + end + if not jsonData.character then + self.importCodeDetail = colorCodes.NEGATIVE.."Invalid JSON format (character missing)" + return + end + jsonData = jsonData.character + if not jsonData.equipment or not jsonData.passives then + self.importCodeDetail = colorCodes.NEGATIVE.."Invalid JSON format (equipment or passives missing)" + return + end + self.importCodeJson = jsonData + self.importCodeDetail = colorCodes.POSITIVE.."JSON is valid" + self.importCodeValid = true + return + end + local xmlText = Inflate(common.base64.decode(buf:gsub("-","+"):gsub("_","/"))) if not xmlText then return @@ -361,6 +606,12 @@ You can get this from your web browser's cookies while logged into the Path of E return end + if self.importCodeJson then + self:ImportItemsAndSkills(self.importCodeJson) + self:ImportPassiveTreeAndJewels(self.importCodeJson) + return + end + importSelectedBuild() end) self.controls.importCodeGo.label = function () @@ -374,33 +625,32 @@ You can get this from your web browser's cookies while logged into the Path of E self.controls.importCodeGo.onClick() end end + + -- -- validate the status of the api the first time + main.api:ValidateAuth(function() end) end) function ImportTabClass:Load(xml, fileName) self.lastRealm = xml.attrib.lastRealm - self.controls.accountRealm:SelByValue(self.lastRealm or main.lastRealm or "PC", "id") - self.lastLeague = xml.attrib.lastLeague - self.controls.charSelectLeague:SelByValue(self.lastLeague or "Standard", "id") self.lastAccountHash = xml.attrib.lastAccountHash self.importLink = xml.attrib.importLink self.controls.enablePartyExportBuffs.state = xml.attrib.exportParty == "true" self.build.partyTab.enableExportBuffs = self.controls.enablePartyExportBuffs.state - if self.lastAccountHash then - for accountName in pairs(main.gameAccounts) do - if common.sha1(accountName) == self.lastAccountHash then - self.controls.accountName:SetText(accountName) - end - end - end - self.lastCharacterHash = xml.attrib.lastCharacterHash + -- if self.lastAccountHash and false then + -- for accountName in pairs(main.gameAccounts) do + -- if common.sha1(accountName) == self.lastAccountHash then + -- self.controls.accountName:SetText(accountName) + -- end + -- end + -- end + -- self.lastCharacterHash = xml.attrib.lastCharacterHash end function ImportTabClass:Save(xml) xml.attrib = { - lastRealm = self.lastRealm, - lastLeague = self.lastLeague, - lastAccountHash = self.lastAccountHash, - lastCharacterHash = self.lastCharacterHash, + -- lastRealm = self.lastRealm, + -- lastAccountHash = self.lastAccountHash, + -- lastCharacterHash = self.lastCharacterHash, exportParty = tostring(self.controls.enablePartyExportBuffs.state), importLink = self.importLink } @@ -425,7 +675,98 @@ function ImportTabClass:Draw(viewPort, inputEvents) self:DrawControls(viewPort) end -function ImportTabClass:DownloadCharacterList() +function ImportTabClass:ProcessSiteJSON(json) + local func, errMsg = loadstring("return " .. jsonToLua(json)) + if errMsg then + return nil, errMsg + end + setfenv(func, {}) -- Sandbox the function just in case + local data = func() + if type(data) ~= "table" then + return nil, "Return type is not a table" + end + return data +end + +function ImportTabClass:SaveAccountHistory() + if not historyList[self.controls.siteAccountName.buf] then + t_insert(historyList, self.controls.siteAccountName.buf) + historyList[self.controls.siteAccountName.buf] = true + table.sort(historyList, function(a, b) + return a:lower() < b:lower() + end) + self.controls.accountHistory:CheckDroppedWidth(true) + self.controls.accountHistory:SelByValue(self.controls.siteAccountName.buf) + end +end + +function ImportTabClass:DownloadPassiveTree() + self.charImportMode = "IMPORTING" + self.charImportStatus = "Retrieving character passive tree..." + local realm = realmList[self.controls.siteAccountRealm.selIndex] + local accountName = self.controls.siteAccountName.buf + local charSelect = self.controls.siteCharSelect + local charListData = charSelect.list[charSelect.selIndex].char + launch:DownloadPage( + realm.hostName .. + "character-window/get-passive-skills?accountName=" .. + accountName:gsub("#", "%%23") .. "&character=" .. urlEncode(charListData.name) .. "&realm=" .. realm.realmCode, + function(response, errMsg) + self.charImportMode = "SELECTCHAR" + if errMsg then + self.charImportStatus = colorCodes.NEGATIVE .. + "Error importing character data, try again (" .. errMsg:gsub("\n", " ") .. ")" + return + elseif response.body == "false" then + self.charImportStatus = colorCodes.NEGATIVE .. "Failed to retrieve character data, try again." + return + end + self.lastCharacterHash = common.sha1(charListData.name) + if not self.lastLeague then + self.lastLeague = charSelectLeague:GetSelValueByKey("league") + end + local responseLua = dkjson.decode(response.body) + -- modify response to be like the oauth API response + local charData = copyTable(charListData) + charData.passives = responseLua + charData.jewels = responseLua.items + self:ImportPassiveTreeAndJewels(charData) + end) +end + +function ImportTabClass:DownloadItems() + self.charImportMode = "IMPORTING" + self.charImportStatus = "Retrieving character items..." + local realm = realmList[self.controls.siteAccountRealm.selIndex] + local accountName = self.controls.siteAccountName.buf + local charSelect = self.controls.siteCharSelect + local charListData = charSelect.list[charSelect.selIndex].char + launch:DownloadPage( + realm.hostName .. + "character-window/get-items?accountName=" .. + accountName:gsub("#", "%%23") .. "&character=" .. urlEncode(charListData.name) .. "&realm=" .. realm.realmCode, + function(response, errMsg) + self.charImportMode = "SELECTCHAR" + if errMsg then + self.charImportStatus = colorCodes.NEGATIVE .. + "Error importing character data, try again (" .. errMsg:gsub("\n", " ") .. ")" + return + elseif response.body == "false" then + self.charImportStatus = colorCodes.NEGATIVE .. "Failed to retrieve character data, try again." + return + end + self.lastCharacterHash = common.sha1(charListData.name) + if not self.lastLeague then + self.lastLeague = charSelectLeague:GetSelValueByKey("league") + end + local responseLua = dkjson.decode(response.body) + -- modify response to be like the oauth API response + local charData = copyTable(charListData) + charData.equipment = responseLua.items + self:ImportItemsAndSkills(charData) + end) +end +function ImportTabClass:DownloadSiteCharacterList() function FindMatchingStandardLeague(league) -- Find a Standard league name for a given league name -- Reference https://api.pathofexile.com/league?realm=pc @@ -441,126 +782,135 @@ function ImportTabClass:DownloadCharacterList() -- normal league and ruthless league (Sanctum, Ruthless Sanctum) return "Standard" end - end - + end + self.charImportMode = "DOWNLOADCHARLIST" self.charImportStatus = "Retrieving character list..." - local realm = realmList[self.controls.accountRealm.selIndex] + local realm = realmList[self.controls.siteAccountRealm.selIndex] local accountName -- Handle spaces in the account name if realm.realmCode == "pc" then - accountName = self.controls.accountName.buf:gsub("%s+", "") + accountName = self.controls.siteAccountName.buf:gsub("%s+", "") else - accountName = self.controls.accountName.buf:gsub("^[%s?]+", ""):gsub("[%s?]+$", ""):gsub("%s", "+") + accountName = self.controls.siteAccountName.buf:gsub("^[%s?]+", ""):gsub("[%s?]+$", ""):gsub("%s", "+") end accountName = accountName:gsub("(.*)[#%-]", "%1#") - local sessionID = #self.controls.sessionInput.buf == 32 and self.controls.sessionInput.buf or (main.gameAccounts[accountName] and main.gameAccounts[accountName].sessionID) - launch:DownloadPage(realm.hostName.."character-window/get-characters?accountName="..accountName:gsub("#", "%%23").."&realm="..realm.realmCode, function(response, errMsg) - if errMsg == "Response code: 401" then - self.charImportStatus = colorCodes.NEGATIVE.."Sign-in is required." - self.charImportMode = "GETSESSIONID" - return - elseif errMsg == "Response code: 403" then - self.charImportStatus = colorCodes.NEGATIVE.."Account profile is private." - self.charImportMode = "GETSESSIONID" - return - elseif errMsg == "Response code: 404" then - self.charImportStatus = colorCodes.NEGATIVE.."Account name is incorrect." - self.charImportMode = "GETACCOUNTNAME" - return - elseif errMsg then - self.charImportStatus = colorCodes.NEGATIVE.."Error retrieving character list, try again ("..errMsg:gsub("\n"," ")..")" - self.charImportMode = "GETACCOUNTNAME" - return - end - local charList, errMsg = self:ProcessJSON(response.body) - if errMsg then - self.charImportStatus = colorCodes.NEGATIVE.."Error processing character list, try again later" - self.charImportMode = "GETACCOUNTNAME" - return - end - --ConPrintTable(charList) - if #charList == 0 then - self.charImportStatus = colorCodes.NEGATIVE.."The account has no characters to import." - self.charImportMode = "GETACCOUNTNAME" - return - end - -- GGG's character API has an issue where for /get-characters the account name is not case-sensitive, but for /get-passive-skills and /get-items it is. - -- This workaround grabs the profile page and extracts the correct account name from one of the URLs. - launch:DownloadPage(realm.hostName..realm.profileURL..accountName:gsub("#", "%%23"), function(response, errMsg) - if errMsg then - self.charImportStatus = colorCodes.NEGATIVE.."Error retrieving character list, try again ("..errMsg:gsub("\n"," ")..")" + launch:DownloadPage( + realm.hostName .. + "character-window/get-characters?accountName=" .. accountName:gsub("#", "%%23") .. "&realm=" .. realm.realmCode, + function(response, errMsg) + if errMsg == "Response code: 401" or errMsg == "Response code: 403" then + self.charImportStatus = colorCodes.NEGATIVE .. "Account profile is private or does not exist." self.charImportMode = "GETACCOUNTNAME" return - end - local realAccountName = response.body:match("/view%-profile/([^/]+)/characters"):gsub(".", function(c) if c:byte(1) > 127 then return string.format("%%%2X",c:byte(1)) else return c end end) - if not realAccountName then - self.charImportStatus = colorCodes.NEGATIVE.."Failed to retrieve character list." - self.charImportMode = "GETSESSIONID" + elseif errMsg == "Response code: 404" then + self.charImportStatus = colorCodes.NEGATIVE .. "Account name is incorrect." + self.charImportMode = "GETACCOUNTNAME" + return + elseif errMsg then + self.charImportStatus = colorCodes.NEGATIVE .. + "Error retrieving character list, try again (" .. errMsg:gsub("\n", " ") .. ")" + self.charImportMode = "GETACCOUNTNAME" return end - realAccountName = realAccountName:gsub("(.*)[#%-]", "%1#") - accountName = realAccountName - self.controls.accountName:SetText(realAccountName) - self.charImportStatus = "Character list successfully retrieved." - self.charImportMode = "SELECTCHAR" - self.lastRealm = realm.id - main.lastRealm = realm.id - self.lastAccountHash = common.sha1(accountName) - main.lastAccountName = accountName - main.gameAccounts[accountName] = main.gameAccounts[accountName] or { } - main.gameAccounts[accountName].sessionID = sessionID - local leagueList = { } - for i, char in ipairs(charList) do - if not isValueInArray(leagueList, char.league) then - t_insert(leagueList, char.league) - end + local charList, errMsg = self:ProcessSiteJSON(response.body) + if errMsg then + self.charImportStatus = colorCodes.NEGATIVE .. "Error processing character list, try again later" + self.charImportMode = "GETACCOUNTNAME" + return end - table.sort(leagueList) - charSelectLeague = self.controls.charSelectLeague - wipeTable(self.controls.charSelectLeague.list) - for _, league in ipairs(leagueList) do - t_insert(self.controls.charSelectLeague.list, { - label = league, - league = league, - }) + --ConPrintTable(charList) + if #charList == 0 then + self.charImportStatus = colorCodes.NEGATIVE .. "The account has no characters to import." + self.charImportMode = "GETACCOUNTNAME" + return end - t_insert(self.controls.charSelectLeague.list, { - label = "All", - }) - -- set the league combo to the last used if possible, used for previously imported characters - if self.lastLeague then - charSelectLeague:SelByValue(self.lastLeague, "league") - -- check that it worked - if charSelectLeague:GetSelValueByKey("league") ~= self.lastLeague then - -- League maybe over, Character will be in standard - standardLeagueName = FindMatchingStandardLeague(self.lastLeague) - self.controls.charSelectLeague:SelByValue(standardLeagueName, "league") - if charSelectLeague:GetSelValueByKey("league") ~= standardLeagueName then - -- give up and select the first entry. Ruthless mode may not have Standard equivalents - charSelectLeague.selIndex = 1 + -- GGG's character API has an issue where for /get-characters the account name is not case-sensitive, but for /get-passive-skills and /get-items it is. + -- This workaround grabs the profile page and extracts the correct account name from one of the URLs. + launch:DownloadPage(realm.hostName .. realm.profileURL .. accountName:gsub("#", "%%23"), + function(response, errMsg) + if errMsg then + self.charImportStatus = colorCodes.NEGATIVE .. + "Error retrieving character list, try again (" .. errMsg:gsub("\n", " ") .. ")" + self.charImportMode = "GETACCOUNTNAME" + return + end + local realAccountName = response.body:match("/view%-profile/([^/]+)/characters"):gsub(".", + function(c) if c:byte(1) > 127 then return string.format("%%%2X", c:byte(1)) else return c end end) + if not realAccountName then + self.charImportStatus = colorCodes.NEGATIVE .. "Failed to retrieve character list." + self.charImportMode = "GETACCOUNTNAME" + return + end + realAccountName = realAccountName:gsub("(.*)[#%-]", "%1#") + accountName = realAccountName + self.controls.siteAccountName:SetText(realAccountName) + self.charImportStatus = "Character list successfully retrieved." + self.charImportMode = "SELECTCHAR" + self.lastRealm = realm.id + main.lastRealm = realm.id + self.lastAccountHash = common.sha1(accountName) + main.lastAccountName = accountName + main.gameAccounts[accountName] = main.gameAccounts[accountName] or {} + main.gameAccounts[accountName].sessionID = sessionID + local leagueList = {} + for i, char in ipairs(charList) do + if not isValueInArray(leagueList, char.league) then + t_insert(leagueList, char.league) + end + end + table.sort(leagueList) + charSelectLeague = self.controls.siteCharSelectLeague + wipeTable(self.controls.siteCharSelectLeague.list) + for _, league in ipairs(leagueList) do + t_insert(self.controls.siteCharSelectLeague.list, { + label = league, + league = league, + }) + end + t_insert(self.controls.siteCharSelectLeague.list, { + label = "All", + }) + -- set the league combo to the last used if possible, used for previously imported characters + if self.lastLeague then + charSelectLeague:SelByValue(self.lastLeague, "league") + -- check that it worked + if charSelectLeague:GetSelValueByKey("league") ~= self.lastLeague then + -- League maybe over, Character will be in standard + standardLeagueName = FindMatchingStandardLeague(self.lastLeague) + self.controls.siteCharSelectLeague:SelByValue(standardLeagueName, "league") + if charSelectLeague:GetSelValueByKey("league") ~= standardLeagueName then + -- give up and select the first entry. Ruthless mode may not have Standard equivalents + charSelectLeague.selIndex = 1 + else + self.lastLeague = standardLeagueName + end + end else - self.lastLeague = standardLeagueName + if self.controls.siteCharSelectLeague.selIndex > #self.controls.siteCharSelectLeague.list then + self.controls.siteCharSelectLeague.selIndex = 1 + end end - end - else - if self.controls.charSelectLeague.selIndex > #self.controls.charSelectLeague.list then - self.controls.charSelectLeague.selIndex = 1 - end - end - self.lastCharList = charList - self:BuildCharacterList(self.controls.charSelectLeague:GetSelValueByKey("league")) + self.lastCharList = charList + self:BuildCharacterList("pc", self.controls.siteCharSelectLeague:GetSelValueByKey("league"), self.lastCharList, self.controls.siteCharSelect) - -- We only get here if the accountname was correct, found, and not private, so add it to the account history. - self:SaveAccountHistory() - end, sessionID and { header = "Cookie: POESESSID=" .. sessionID }) - end, sessionID and { header = "Cookie: POESESSID=" .. sessionID }) + -- We only get here if the accountname was correct, found, and not private, so add it to the account history. + self:SaveAccountHistory() + end) + end) end -function ImportTabClass:BuildCharacterList(league) - wipeTable(self.controls.charSelect.list) - for i, char in ipairs(self.lastCharList) do - if not league or char.league == league then +--- @param realm string +--- @param league string +--- @param characters table? +--- @param control table +function ImportTabClass:BuildCharacterList(realm, league, characters, control) + wipeTable(control.list) + if not characters then + return + end + for i, char in ipairs(characters) do + if realm == char.realm and (not league) or char.league == league then charLvl = char.level or 0 charLeague = char.league or "?" charName = char.name or "?" @@ -568,31 +918,8 @@ function ImportTabClass:BuildCharacterList(league) classColor = colorCodes.DEFAULT if charClass ~= "?" then - classColor = colorCodes[charClass:upper()] - - if classColor == nil then - if (charClass == "Elementalist" or charClass == "Necromancer" or charClass == "Occultist" or - charClass == "Harbinger" or charClass == "Herald" or charClass == "Bog Shaman") then - classColor = colorCodes["WITCH"] - elseif (charClass == "Guardian" or charClass == "Inquisitor" or charClass == "Hierophant" or - charClass == "Architect of Chaos" or charClass == "Polytheist" or charClass == "Puppeteer") then - classColor = colorCodes["TEMPLAR"] - elseif (charClass == "Assassin" or charClass == "Trickster" or charClass == "Saboteur" or - charClass == "Surfcaster" or charClass == "Servant of Arakaali" or charClass == "Blind Prophet") then - classColor = colorCodes["SHADOW"] - elseif (charClass == "Gladiator" or charClass == "Slayer" or charClass == "Champion" or - charClass == "Gambler" or charClass == "Paladin" or charClass == "Aristocrat") then - classColor = colorCodes["DUELIST"] - elseif (charClass == "Raider" or charClass == "Pathfinder" or charClass == "Deadeye" or charClass == "Warden" or - charClass == "Daughter of Oshabi" or charClass == "Whisperer" or charClass == "Wildspeaker") then - classColor = colorCodes["RANGER"] - elseif (charClass == "Juggernaut" or charClass == "Berserker" or charClass == "Chieftain" or - charClass == "Antiquarian" or charClass == "Behemoth" or charClass == "Ancestral Commander") then - classColor = colorCodes["MARAUDER"] - elseif (charClass == "Ascendant" or charClass == "Reliquarian" or charClass == "Scavenger") then - classColor = colorCodes["SCION"] - end - end + local tree = main:LoadTree(latestTreeVersion .. (char.league:match("Ruthless") and "_ruthless" or "")) + classColor = colorCodes[charClass:upper()] or colorCodes[tree.ascendNameMap[charClass].class.name:upper()] or "^7" end local detail @@ -601,7 +928,7 @@ function ImportTabClass:BuildCharacterList(league) else detail = string.format("%s%s ^x808080lvl %d", classColor, charClass, charLvl) end - t_insert(self.controls.charSelect.list, { + t_insert(control.list, { label = charName, char = char, searchFilter = charName.." "..charClass, @@ -609,119 +936,76 @@ function ImportTabClass:BuildCharacterList(league) }) end end - table.sort(self.controls.charSelect.list, function(a,b) + table.sort(control.list, function(a,b) return a.char.name:lower() < b.char.name:lower() end) - self.controls.charSelect.selIndex = 1 + control.selIndex = 1 if self.lastCharacterHash then - for i, char in ipairs(self.controls.charSelect.list) do + for i, char in ipairs(control.list) do if common.sha1(char.char.name) == self.lastCharacterHash then - self.controls.charSelect.selIndex = i + control.selIndex = i break end end end end - -function ImportTabClass:SaveAccountHistory() - if not historyList[self.controls.accountName.buf] then - t_insert(historyList, self.controls.accountName.buf) - historyList[self.controls.accountName.buf] = true - table.sort(historyList, function(a,b) - return a:lower() < b:lower() - end) - self.controls.accountHistory:CheckDroppedWidth(true) - self.controls.accountHistory:SelByValue(self.controls.accountName.buf) - end -end - -function ImportTabClass:DownloadPassiveTree() - self.charImportMode = "IMPORTING" - self.charImportStatus = "Retrieving character passive tree..." - local realm = realmList[self.controls.accountRealm.selIndex] - local accountName = self.controls.accountName.buf - local sessionID = #self.controls.sessionInput.buf == 32 and self.controls.sessionInput.buf or (main.gameAccounts[accountName] and main.gameAccounts[accountName].sessionID) - local charSelect = self.controls.charSelect - local charData = charSelect.list[charSelect.selIndex].char - launch:DownloadPage(realm.hostName.."character-window/get-passive-skills?accountName="..accountName:gsub("#", "%%23").."&character="..urlEncode(charData.name).."&realm="..realm.realmCode, function(response, errMsg) - self.charImportMode = "SELECTCHAR" - if errMsg then - self.charImportStatus = colorCodes.NEGATIVE.."Error importing character data, try again ("..errMsg:gsub("\n"," ")..")" - return - elseif response.body == "false" then - self.charImportStatus = colorCodes.NEGATIVE.."Failed to retrieve character data, try again." - return - end - self.lastCharacterHash = common.sha1(charData.name) - if not self.lastLeague then - self.lastLeague = charSelectLeague:GetSelValueByKey("league") - end - self:ImportPassiveTreeAndJewels(response.body, charData) - end, sessionID and { header = "Cookie: POESESSID=" .. sessionID }) -end - -function ImportTabClass:DownloadItems() - self.charImportMode = "IMPORTING" - self.charImportStatus = "Retrieving character items..." - local realm = realmList[self.controls.accountRealm.selIndex] - local accountName = self.controls.accountName.buf - local sessionID = #self.controls.sessionInput.buf == 32 and self.controls.sessionInput.buf or (main.gameAccounts[accountName] and main.gameAccounts[accountName].sessionID) - local charSelect = self.controls.charSelect - local charData = charSelect.list[charSelect.selIndex].char - launch:DownloadPage(realm.hostName.."character-window/get-items?accountName="..accountName:gsub("#", "%%23").."&character="..urlEncode(charData.name).."&realm="..realm.realmCode, function(response, errMsg) - self.charImportMode = "SELECTCHAR" - if errMsg then - self.charImportStatus = colorCodes.NEGATIVE.."Error importing character data, try again ("..errMsg:gsub("\n"," ")..")" - return - elseif response.body == "false" then - self.charImportStatus = colorCodes.NEGATIVE.."Failed to retrieve character data, try again." - return - end - self.lastCharacterHash = common.sha1(charData.name) - if not self.lastLeague then - self.lastLeague = charSelectLeague:GetSelValueByKey("league") - end - self:ImportItemsAndSkills(response.body) - end, sessionID and { header = "Cookie: POESESSID=" .. sessionID }) -end - -function ImportTabClass:ImportPassiveTreeAndJewels(json, charData) - --local out = io.open("get-passive-skills.json", "w") - --out:write(json) - --out:close() - local charPassiveData, errMsg = self:ProcessJSON(json) - --local out = io.open("get-passive-skills.json", "w") - --writeLuaTable(out, charPassiveData, 1) - --out:close() - +-- https://www.pathofexile.com/developer/docs/reference#type-Character +--- @class CharacterBasicData +--- @field id string? not present on website +--- @field name string +--- @field realm "pc" | "xbox" | "sony" +--- @field class string +--- @field league string +--- @field level integer +--- @field experience integer +--- +--- @alias Bandits "Kraityn" | "Alira" | "Oak" | "Eramir" +--- @alias MajorPantheon "TheBrineKing" | " Arakaali" | " Solaris" | "Lunaris" +--- @alias MinorPantheon "Abberath" | " Gruthkul" | " Yugul" | " Shakari" | " Tukohama" | " Ralakesh" | " Garukhan" | "Ryslatha" + + +--- @class CharacterPassives +--- @field mastery_effects table +--- @field skill_overrides table +--- @field jewel_data table +--- @field hashes_ex integer[] +--- @field hashes integer[] +--- @field bandit_choice Bandits? +--- @field pantheon_major MajorPantheon? +--- @field pantheon_minor MinorPantheon? +--- @field alternate_ascendancy string | integer integer on website, string on oauth + +-- https://www.pathofexile.com/developer/docs/reference#type-Item +--- @alias Item any + +--- @class CharacterPassivesData : CharacterBasicData +--- @field jewels Item[] +--- @field passives CharacterPassives +--- @param charData CharacterPassivesData +--- @return string +function ImportTabClass:ImportPassiveTreeAndJewels(charData) + local charPassives = charData.passives -- 3.16+ - if charPassiveData.mastery_effects then - local mastery, effect = 0, 0 - for key, value in pairs(charPassiveData.mastery_effects) do - if type(value) ~= "string" then - break - end - mastery = band(tonumber(value), 65535) - effect = b_rshift(tonumber(value), 16) - t_insert(charPassiveData.mastery_effects, mastery, effect) + local mastery, effect = 0, 0 + for key, value in pairs(charPassives.mastery_effects) do + if type(value) ~= "string" then + break end + mastery = band(tonumber(value), 65535) + effect = b_rshift(tonumber(value), 16) + t_insert(charPassives.mastery_effects, mastery, effect) end - if charPassiveData.skill_overrides then - for nodeId, override in pairs(charPassiveData.skill_overrides) do - self.build.spec:ReplaceNode(override, self.build.spec.tree.tattoo.nodes[override.name]) - override.id = nodeId - end + for nodeId, override in pairs(charData.passives.skill_overrides) do + self.build.spec:ReplaceNode(override, self.build.spec.tree.tattoo.nodes[override.name]) + override.id = nodeId end if errMsg then - self.charImportStatus = colorCodes.NEGATIVE.."Error processing character data, try again later." - return + return colorCodes.NEGATIVE.."Error processing character data, try again later." end - self.charImportStatus = colorCodes.POSITIVE.."Passive tree and jewels successfully imported." - self.build.spec.jewel_data = copyTable(charPassiveData.jewel_data) - self.build.spec.extended_hashes = copyTable(charPassiveData.hashes_ex) - --ConPrintTable(charPassiveData) + self.build.spec.jewel_data = copyTable(charPassives.jewel_data) + self.build.spec.extended_hashes = copyTable(charPassives.hashes_ex) if self.controls.charImportTreeClearJewels.state then for _, slot in pairs(self.build.itemsTab.slots) do if slot.selItemId ~= 0 and slot.nodeId then @@ -730,46 +1014,41 @@ function ImportTabClass:ImportPassiveTreeAndJewels(json, charData) end end end - for _, itemData in pairs(charPassiveData.items) do + for _, itemData in ipairs(charData.jewels) do self:ImportItem(itemData) end self.build.itemsTab:PopulateSlots() self.build.itemsTab:AddUndoState() - -- Alternate trees don't have an identifier, so we're forced to look up something that is unique to that tree - -- Hopefully this changes, because it's totally unmaintainable - local function isAscendancyInTree(className, treeVersion) - local classes = main.tree[treeVersion].classes - for _, class in pairs(classes) do - if class.name == className then - return true - end - for i = 0, #class.classes do - local ascendClass = class.classes[i] - if ascendClass.name == className then - return true - end - end + + local alternateAscendancyId + if charPassives.alternate_ascendancy then + -- oauth responses have bloodline names + if type(charPassives.alternate_ascendancy) == "string" then + local bloodline = self.build.latestTree.secondaryAscendNameMap[charPassives.alternate_ascendancy] + alternateAscendancyId = bloodline and bloodline.ascendClassId + -- site responses have integer ids + else + alternateAscendancyId = charPassives.alternate_ascendancy end + else + alternateAscendancyId = 0 end - -- Character import uses current GGG cluster hashes. self.build.spec.clusterHashFormatVersion = 2 - self.build.spec:ImportFromNodeList(charPassiveData.character, - charPassiveData.ascendancy, - charPassiveData.alternate_ascendancy or 0, - charPassiveData.hashes, - charPassiveData.skill_overrides, - charPassiveData.mastery_effects or {}, - latestTreeVersion .. (charData.league:match("Ruthless") and "_ruthless" or "") .. (isAscendancyInTree(charData.class, latestTreeVersion) and "" or "_alternate") - ) + self.build.spec:ImportFromNodeList(charData.class, + nil, + nil, + alternateAscendancyId, + charPassives.hashes, + charPassives.skill_overrides, + charPassives.mastery_effects or {}, + latestTreeVersion .. (charData.league:match("Ruthless") and "_ruthless" or "") + ) self.build.treeTab:SetActiveSpec(self.build.treeTab.activeSpec) self.build.spec:BuildClusterJewelGraphs() self.build.spec:AddUndoState() - if not self.lastLeague then - self.lastLeague = charSelectLeague:GetSelValueByKey("league") - end - self.build.characterLevel = charData.level + self.build.characterLevel = charData.level or 100 self.build.characterLevelAutoMode = false self.build.configTab:UpdateLevel() self.build.controls.characterLevel:SetText(charData.level) @@ -784,7 +1063,31 @@ function ImportTabClass:ImportPassiveTreeAndJewels(json, charData) end end self.build.configTab.varControls["resistancePenalty"]:SetSel(resistancePenaltyIndex) - main:SetWindowTitleSubtext(string.format("%s (%s, %s, %s)", self.build.buildName, charData.name, charData.class, charData.league)) + + local function setSelByVal(dropdown, val) + for i, v in ipairs(dropdown.list) do + if v.val == val then + dropdown:SetSel(i) + end + end + end + + local bandit = (charPassives.bandit_choice == "Eramir" or not charPassives.bandit_choice) and "None" or + charPassives.bandit_choice + setSelByVal(self.build.configTab.varControls["bandit"], + bandit) + + local majorGod = charPassives.pantheon_major or "None" + setSelByVal(self.build.configTab.varControls["pantheonMajorGod"], + majorGod) + + local minorGod = charPassives.pantheon_minor or "None" + setSelByVal(self.build.configTab.varControls["pantheonMinorGod"], + minorGod) + + main:SetWindowTitleSubtext(string.format("%s (%s, %s, %s)", self.build.buildName, charData.name, charData.class, + charData.league)) + return colorCodes.POSITIVE.."Passive tree and jewels successfully imported." end local SOCKET_GROUP_REIMPORT_KEY_SEPARATOR = "\31" @@ -872,15 +1175,11 @@ local function applySocketGroupReimportState(socketGroup, state) end end -function ImportTabClass:ImportItemsAndSkills(json) - --local out = io.open("get-items.json", "w") - --out:write(json) - --out:close() - local charItemData, errMsg = self:ProcessJSON(json) - if errMsg then - self.charImportStatus = colorCodes.NEGATIVE.."Error processing character data, try again later." - return - end +--- @class CharacterItemsData : CharacterBasicData +--- @field equipment Item[] +--- @param charData CharacterItemsData +--- @return CharacterItemsData, string +function ImportTabClass:ImportItemsAndSkills(charData) if self.controls.charImportItemsClearItems.state then for _, slot in pairs(self.build.itemsTab.slots) do if slot.selItemId ~= 0 and not slot.nodeId then @@ -910,9 +1209,7 @@ function ImportTabClass:ImportItemsAndSkills(json) wipeTable(self.build.skillsTab.socketGroupList) self.build.skillsTab:RebuildImbuedSupportBySlot() end - self.charImportStatus = colorCodes.POSITIVE.."Items and skills successfully imported." - --ConPrintTable(charItemData) - for _, itemData in pairs(charItemData.items) do + for _, itemData in ipairs(charData.equipment) do self:ImportItem(itemData) end if skillOrder then @@ -974,11 +1271,12 @@ function ImportTabClass:ImportItemsAndSkills(json) self.build.itemsTab:PopulateSlots() self.build.itemsTab:AddUndoState() self.build.skillsTab:AddUndoState() - self.build.characterLevel = charItemData.character.level + self.build.characterLevel = charData.level self.build.configTab:UpdateLevel() - self.build.controls.characterLevel:SetText(charItemData.character.level) + self.build.controls.characterLevel:SetText(tostring(charData.level)) self.build.buildFlag = true - return charItemData.character -- For the wrapper + -- charData for the wrapper + return charData, colorCodes.POSITIVE .. "Items and skills successfully imported." end local rarityMap = { [0] = "NORMAL", "MAGIC", "RARE", "UNIQUE", [9] = "RELIC", [10] = "RELIC" } @@ -1382,23 +1680,10 @@ function UrlDecode(url) return url end -function ImportTabClass:ProcessJSON(json) - local func, errMsg = loadstring("return "..jsonToLua(json)) - if errMsg then - return nil, errMsg - end - setfenv(func, { }) -- Sandbox the function just in case - local data = func() - if type(data) ~= "table" then - return nil, "Return type is not a table" - end - return data -end - function ImportTabClass:SetPredefinedBuildName() - local accountName = self.controls.accountName.buf:gsub('%s+', ''):gsub("#%d+", "") - local charSelect = self.controls.charSelect + local accountName = self.controls.siteAccountName.buf:gsub('%s+', ''):gsub("#%d+", "") + local charSelect = self.controls.siteCharSelect local charData = charSelect.list[charSelect.selIndex].char local charName = charData.name - main.predefinedBuildName = accountName.." - "..charName + main.predefinedBuildName = accountName .. " - " .. charName end diff --git a/src/Classes/PassiveSpec.lua b/src/Classes/PassiveSpec.lua index e28e74a86b..38ebfbb8ae 100644 --- a/src/Classes/PassiveSpec.lua +++ b/src/Classes/PassiveSpec.lua @@ -176,7 +176,7 @@ function PassiveSpecClass:Load(xml, dbFileName) end end end - self:ImportFromNodeList(tonumber(xml.attrib.classId), tonumber(xml.attrib.ascendClassId), tonumber(xml.attrib.secondaryAscendClassId or 0), hashList, self.hashOverrides, masteryEffects) + self:ImportFromNodeList(nil, tonumber(xml.attrib.classId), tonumber(xml.attrib.ascendClassId), tonumber(xml.attrib.secondaryAscendClassId or 0), hashList, self.hashOverrides, masteryEffects) elseif url then self:DecodeURL(url) end @@ -242,13 +242,20 @@ function PassiveSpecClass:PostLoad() end -- Import passive spec from the provided class IDs and node hash list -function PassiveSpecClass:ImportFromNodeList(classId, ascendClassId, secondaryAscendClassId, hashList, hashOverrides, masteryEffects, treeVersion) +function PassiveSpecClass:ImportFromNodeList(className, classId, ascendClassId, secondaryAscendClassId, hashList, hashOverrides, masteryEffects, treeVersion) if hashOverrides == nil then hashOverrides = {} end if treeVersion and treeVersion ~= self.treeVersion then self:Init(treeVersion) self.build.treeTab.showConvert = self.treeVersion ~= latestTreeVersion end self:ResetNodes() + if className then + classId = self.tree.classNameMap[className] or + (self.tree.ascendNameMap[className] and self.tree.ascendNameMap[className].classId) or + (self.tree.internalAscendNameMap[className] and self.tree.internalAscendNameMap[className].classId) + ascendClassId = (self.tree.ascendNameMap[className] and self.tree.ascendNameMap[className].ascendClassId) or + (self.tree.internalAscendNameMap[className] and self.tree.internalAscendNameMap[className].ascendClassId) or 0 + end self:SelectClass(classId) self:SelectAscendClass(ascendClassId) self:SelectSecondaryAscendClass(secondaryAscendClassId) @@ -1922,7 +1929,7 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe if proxyGroup == data.group then if node.oidx == data.orbitIndex and not data.isMastery then for _, extendedId in ipairs(importedGroups[proxyGroup].nodes) do - if id == tonumber(extendedId) and inExtendedHashes(id) then + if id == extendedId and inExtendedHashes(tonumber(id)) then return true end end @@ -2248,7 +2255,7 @@ function PassiveSpecClass:CreateUndoState() end function PassiveSpecClass:RestoreUndoState(state, treeVersion) - self:ImportFromNodeList(state.classId, state.ascendClassId, state.secondaryAscendClassId, state.hashList, state.hashOverrides, state.masteryEffects, treeVersion or state.treeVersion) + self:ImportFromNodeList(nil, state.classId, state.ascendClassId, state.secondaryAscendClassId, state.hashList, state.hashOverrides, state.masteryEffects, treeVersion or state.treeVersion) self:SetWindowTitleWithBuildClass() end diff --git a/src/Classes/PassiveTree.lua b/src/Classes/PassiveTree.lua index 6ea7da09a0..3e0314643e 100644 --- a/src/Classes/PassiveTree.lua +++ b/src/Classes/PassiveTree.lua @@ -103,7 +103,8 @@ local PassiveTreeClass = newClass("PassiveTree", function(self, treeVersion) -- Build maps of class name -> class table self.classNameMap = { } self.ascendNameMap = { } - self.classNotables = { } + self.internalAscendNameMap = {} + self.classNotables = {} for classId, class in pairs(self.classes) do if versionNum >= 3.10 then @@ -121,6 +122,14 @@ local PassiveTreeClass = newClass("PassiveTree", function(self, treeVersion) flavourText = ascendClass.flavourText, flavourTextRect = ascendClass.flavourTextRect, } + if ascendClass.internalId then + self.internalAscendNameMap[ascendClass.internalId] = { + classId = classId, + class = class, + ascendClassId = ascendClassId, + ascendClass = ascendClass + } + end end end diff --git a/src/Classes/PoEAPI.lua b/src/Classes/PoEAPI.lua new file mode 100644 index 0000000000..1b469c17fc --- /dev/null +++ b/src/Classes/PoEAPI.lua @@ -0,0 +1,217 @@ +local base64 = require("base64") +local sha = require("sha2") +local dkjson = require "dkjson" + +local scopesOAuth = { + "account:profile", + "account:leagues", + "account:characters", + "account:trade" +} + +local filename = "poe_api_response.json" + +local PoEAPIClass = newClass("PoEAPI", function(self, authToken, refreshToken, tokenExpiry) + self.retries = 0 + self.authToken = authToken + self.refreshToken = refreshToken + self.tokenExpiry = tokenExpiry or 0 + self.baseUrl = "https://api.pathofexile.com" + self.rateLimiter = new("TradeQueryRateLimiter") + + self.ERROR_NO_AUTH = "No auth token" +end) + + +--- @param callback fun(valid: bool, updateSettings: bool) +function PoEAPIClass:ValidateAuth(callback) + ConPrintf("Validating auth token") + -- make a call for profile if not error we are good + -- if error 401 then try to recreate the token with + if self.authToken and self.refreshToken and self.tokenExpiry then + if self.tokenExpiry < os.time() then + ConPrintf("Auth token expired") + -- here recreate the token with the refresh_token + local formText = "client_id=pob&grant_type=refresh_token&refresh_token=" .. self.refreshToken + launch:DownloadPage("https://www.pathofexile.com/oauth/token", function(response, errMsg) + ConPrintf("Recreating auth token") + if errMsg then + ConPrintf("Failed to recreate auth token: %s", errMsg) + callback(false, false) + return + end + local responseLua = dkjson.decode(response.body) + self.authToken = responseLua.access_token + self.refreshToken = responseLua.refresh_token + self.tokenExpiry = os.time() + responseLua.expires_in + self:UpdateMain() + self.retries = 0 + callback(true, true) + end, { body = formText }) + else + callback(true, false) + end + else + callback(false, false) + end +end + +--- @param secret string +local function base64_encode(secret) + return base64.encode(secret):gsub("+", "-"):gsub("/", "_"):gsub("=$", "") +end + +--- resets current authorization details +function PoEAPIClass:ResetDetails() + self.authToken = nil + self.refreshToken = nil + self.tokenExpiry = nil + self:UpdateMain() +end + +--- updates main so that API details are saved across restarts +function PoEAPIClass:UpdateMain() + main.lastToken = self.authToken + main.lastRefreshToken = self.refreshToken + main.tokenExpiry = self.tokenExpiry + main:SaveSettings() +end + +--- @param callback fun(errCode: string?) +function PoEAPIClass:FetchAuthToken(callback) + math.randomseed(os.time()) + local secret = math.random(2 ^ 32 - 1) + local code_verifier = base64_encode(tostring(secret)) + local code_challenge = base64_encode(sha.hex_to_bin(sha.sha256(code_verifier))) + + -- 16 character hex string + local initialState = string.gsub('xxxxxxxxxxxxxxxx', 'x', function() + return string.format('%x', math.random(0, 0xf)) + end) + + local authUrl = string.format( + "https://www.pathofexile.com/oauth/authorize?client_id=pob&response_type=code&scope=%s&state=%s&code_challenge=%s&code_challenge_method=S256" + , table.concat(scopesOAuth, "%20") + , initialState + , code_challenge + ) + + local server = io.open("LaunchServer.lua", "r") + local id = LaunchSubScript(server:read("*a"), "", "ConPrintf,OpenURL", authUrl) + if id then + launch.subScripts[id] = { + type = "DOWNLOAD", + callback = function(code, errMsg, state, port) + if not code then + ConPrintf("Failed to get code from server: %s", errMsg) + self:ResetDetails() + callback(self.ERROR_NO_AUTH) + return + end + + if initialState ~= state then + return + end + local formText = "client_id=pob&grant_type=authorization_code&code=" .. + code .. + "&redirect_uri=http://localhost:" .. + port .. "&scope=" .. table.concat(scopesOAuth, " ") .. "&code_verifier=" .. code_verifier + launch:DownloadPage("https://www.pathofexile.com/oauth/token", function(response, errMsg) + if errMsg then + ConPrintf("Failed to get token from server: " .. errMsg) + self:ResetDetails() + callback() + return + end + local responseLua = dkjson.decode(response.body) + self.authToken = responseLua.access_token + self.refreshToken = responseLua.refresh_token + self.tokenExpiry = os.time() + responseLua.expires_in + self:UpdateMain() + self.retries = 0 + SetForeground() + callback() + end, { body = formText }) + end + } + end +end + +--- @param endpoint string +--- @param callback fun(response: table?, errorMsg: string, updateSettings: bool) +function PoEAPIClass:DownloadWithRefresh(endpoint, callback) + self:ValidateAuth(function(valid, updateSettings) + if not valid then + -- Clean info about token and refresh token + self:ResetDetails() + callback(nil, self.ERROR_NO_AUTH, true) + return + end + + launch:DownloadPage(self.baseUrl .. endpoint, function(response, errMsg) + if errMsg and errMsg:match("401") and self.retries < 1 then + -- try once again with refresh token + self.retries = 1 + self.tokenExpiry = 0 + self:DownloadWithRefresh(endpoint, callback) + else + self.retries = 0 + if errMsg then + ConPrintf("Failed to download %s: %s", endpoint, errMsg) + elseif response and response.body then + -- create the file and log the name file + local file = io.open(filename, "w") + if file then + file:write(response.body) + file:close() + end + ConPrintf("Download %s:\n%s\n", endpoint, filename) + end + callback(response, errMsg, updateSettings) + end + end, { header = "Authorization: Bearer " .. self.authToken }) + end) +end + +--- @alias DownloadCallback fun(body: table?, err: string?, timeout: integer?) +--- @param policy string +--- @param url string +--- @param callback DownloadCallback +function PoEAPIClass:DownloadWithRateLimit(policy, url, callback) + local now = os.time() + local timeNext = self.rateLimiter:NextRequestTime(policy, now) + if now >= timeNext then + local requestId = self.rateLimiter:InsertRequest(policy) + local onComplete = function(response, errMsg) + self.rateLimiter:FinishRequest(policy, requestId) + self.rateLimiter:UpdateFromHeader(response.header, policy) + if response.header:match("HTTP/[%d%.]+ (%d+)") == "429" then + timeNext = self.rateLimiter:NextRequestTime(policy, now) + callback(nil, "Response code: 429", timeNext) + return + end + local responseLua = dkjson.decode(response.body) + callback(responseLua, errMsg, nil) + end + self:DownloadWithRefresh(url, onComplete) + else + callback(nil, "Response code: 429", timeNext) + end +end + +---Fetches character list from PoE's OAuth api +---@param realm string Realm to fetch the list from +---@param callback DownloadCallback +function PoEAPIClass:DownloadCharacterList(realm, callback) + self:DownloadWithRateLimit("character-list-request-limit", + "/character" .. (realm == "pc" and "" or "/" .. realm), callback) +end + +---Fetches character from PoE's OAuth api +---@param realm string Realm to fetch the character from +---@param name string Character name to fetch +---@param callback DownloadCallback +function PoEAPIClass:DownloadCharacter(realm, name, callback) + self:DownloadWithRateLimit("character-request-limit", + "/character" .. (realm == "pc" and "" or "/" .. realm) .. "/" .. name, callback) +end diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 9e1308bfb9..430dd6ef5e 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -34,8 +34,9 @@ local TradeQueryClass = newClass("TradeQuery", function(self, itemsTab) -- default set of trade item sort selection self.slotTables = { } self.pbItemSortSelectionIndex = 1 + -- for each league, a table of values of each currency in div + --- @type table> self.pbCurrencyConversion = { } - self.currencyConversionTradeMap = { } self.lastCurrencyConversionRequest = 0 self.lastCurrencyFileTime = { } self.pbFileTimestampDiff = { } @@ -45,13 +46,44 @@ local TradeQueryClass = newClass("TradeQuery", function(self, itemsTab) -- table holding all realm/league pairs. (allLeagues[realm] = [league.id,...]) self.allLeagues = {} -- realm id-text table to pair realm name with API parameter - self.realmIds = {} + self.realmIds = { + ["PC"] = "pc", + ["Xbox"] = "xbox", + ["Sony"] = "sony" + } + --- @type integer? + self.backoffFinish = nil -- last query for each row self.lastQueries = {} self.tradeQueryRequests = new("TradeQueryRequests") + local function onRateLimit(backoff) + self.backoffFinish = get_time() + backoff + self.countDown = coroutine.create(function() + while self.backoffFinish do + local now = get_time() + if self.backoffFinish < (now + 0.5) then + self.backoffFinish = nil + self:SetNotice(self.controls.pbNotice, "") + return + end + local msg = s_format("Rate limited. Retrying after %s seconds...", self.backoffFinish - now) + self:SetNotice(self.controls.pbNotice, colorCodes.WARNING..msg) + coroutine.yield() + end + end) + end main.onFrameFuncs["TradeQueryRequests"] = function() - self.tradeQueryRequests:ProcessQueue() + self.tradeQueryRequests:ProcessQueue(onRateLimit) + if self.countDown then + coroutine.resume(self.countDown) + if coroutine.status(self.countDown) == "dead" then + self.countDown = nil + end + end + end + if not main.api then + main.api = new("PoEAPI", main.lastToken, main.lastRefreshToken, main.tokenExpiry) end -- set @@ -121,20 +153,13 @@ function TradeQueryClass:PullLeagueList() end) end --- Method to convert currency to chaos equivalent -function TradeQueryClass:ConvertCurrencyToChaos(currency, amount) - local conversionTable = self.pbCurrencyConversion[self.pbLeague] - - -- we take the ceiling of all prices to integer chaos - -- to prevent dealing with shenanigans of people asking 4.9 chaos - if conversionTable and conversionTable[currency:lower()] then - --ConPrintf("Converted '"..currency.."' at " ..tostring(conversionTable[currency:lower()])) - return m_ceil(amount * conversionTable[currency:lower()]) - elseif currency:lower() == "chaos" then - return m_ceil(amount) - else - ConPrintf("Unhandled Currency Conversion: '" .. currency:lower() .. "'") - return nil +--- @param currencyId string +--- @param amount integer +--- @return number? +function TradeQueryClass:ConvertCurrencyToDivs(currencyId, amount) + local map = self.pbCurrencyConversion[self.pbLeague] + if map and map[currencyId] then + return amount * map[currencyId] end end @@ -146,60 +171,62 @@ function TradeQueryClass:PullPoENinjaCurrencyConversion(league) self:SetNotice(self.controls.pbNotice, "PoE Ninja Rate Limit Exceeded: " .. tostring(3600 - (now - self.lastCurrencyConversionRequest))) return end - -- We are getting currency short-names from Poe API before getting PoeNinja rates - -- Potentially, currency short-names could be cached but this request runs - -- once per hour at most and the Poe API response is already Cloudflare cached - self:FetchCurrencyConversionTable(function(data, errMsg) - if errMsg then - self:SetNotice(self.controls.pbNotice, "Error: " .. tostring(errMsg)) - return - end - self.pbCurrencyConversion[league] = { } - self.lastCurrencyConversionRequest = now - launch:DownloadPage( - "https://poe.ninja/api/data/CurrencyRates?league=" .. urlEncode(league), - function(response, errMsg) - if errMsg then - self:SetNotice(self.controls.pbNotice, "Error: " .. tostring(errMsg)) - return - end - local json_data = dkjson.decode(response.body) - if not json_data then - self:SetNotice(self.controls.pbNotice, "Failed to Get PoE Ninja response") - return - end - self:PriceBuilderProcessPoENinjaResponse(json_data, self.controls) - local print_str = "" - for key, value in pairs(self.pbCurrencyConversion[self.pbLeague]) do - print_str = print_str .. '"'..key..'": '..tostring(value)..',' - end - local foo = io.open("../"..self.pbLeague.."_currency_values.json", "w") - foo:write("{" .. print_str .. '"updateTime": ' .. tostring(get_time()) .. "}") - foo:close() - self:SetCurrencyConversionButton() - end) - end) + + self.pbCurrencyConversion[league] = { } + self.lastCurrencyConversionRequest = now + launch:DownloadPage( + "https://poe.ninja/poe1/api/economy/exchange/current/overview?type=Currency&league=" .. urlEncode(league), + function(response, errMsg) + if errMsg then + self:SetNotice(self.controls.pbNotice, "Error: " .. tostring(errMsg)) + return + end + local json_data = dkjson.decode(response.body) + if not json_data or not json_data.lines then + self:SetNotice(self.controls.pbNotice, "Failed to Get PoE Ninja response") + return + end + if not self:PriceBuilderProcessPoENinjaResponse(json_data.lines) then + -- don't edit json on failure + return + end + local print_str = "" + for key, value in pairs(self.pbCurrencyConversion[self.pbLeague]) do + print_str = print_str .. '"'..key..'": '..tostring(value)..',' + end + local foo = io.open("../"..self.pbLeague.."_currency_values.json", "w") + foo:write("{" .. print_str .. '"updateTime": ' .. tostring(get_time()) .. "}") + foo:close() + self:SetCurrencyConversionButton() + end) + end -- Method to process the PoE.Ninja response -function TradeQueryClass:PriceBuilderProcessPoENinjaResponse(resp) - if resp then - -- Populate the chaos-converted values for each tradeId - for currencyName, chaosEquivalent in pairs(resp) do - local currencyName = currencyName:lower() - if self.currencyConversionTradeMap[currencyName] then - self.pbCurrencyConversion[self.pbLeague][self.currencyConversionTradeMap[currencyName]] = chaosEquivalent - else - ConPrintf("Unhandled Currency Name: '"..currencyName.."'") - end +--- @param responseLines table[] +--- @return bool +function TradeQueryClass:PriceBuilderProcessPoENinjaResponse(responseLines) + -- Populate the divine-converted values for each tradeId + for _, currencyDetails in ipairs(responseLines) do + -- these use the same ids as the trade site, which are also short + -- readable names, like "transmute" or "aug", which means there's no + -- need for conversion. + local id = currencyDetails.id + -- poe.ninja uses divs as the primary currency, and as far as I know, + -- this figure is equivalent to the best ratio in equivalent divs + local divs = currencyDetails.primaryValue + if not id or not divs then + self:SetNotice(self.controls.pbNotice, "Currencies not updated: malformed PoE Ninja response") + return false end - -- if nothing was actually found, we should add a notice - if next(self.pbCurrencyConversion[self.pbLeague]) == nil then - self:SetNotice(self.controls.pbNotice, "No currencies received from PoE Ninja") - end - else - self:SetNotice(self.controls.pbNotice, "PoE Ninja JSON Processing Error") + self.pbCurrencyConversion[self.pbLeague][id] = divs + end + -- if nothing was actually found, we should add a notice + if next(self.pbCurrencyConversion[self.pbLeague]) == nil then + self:SetNotice(self.controls.pbNotice, "No currencies received from PoE Ninja") + return false end + return true end local function initStatSortSelectionList(list) @@ -250,35 +277,71 @@ function TradeQueryClass:PriceItem() return #self.itemsTab.itemSetOrderList > 1 end - self.controls.poesessidButton = new("ButtonControl", {"TOPLEFT", self.controls.setSelect, "TOPLEFT"}, {0, row_height + row_vertical_padding, 188, row_height}, function() return main.POESESSID ~= "" and "^2Session Mode" or colorCodes.WARNING.."No Session Mode" end, function() - local poesessid_controls = {} - poesessid_controls.sessionInput = new("EditControl", nil, {0, 18, 350, 18}, main.POESESSID, nil, "%X", 32) - poesessid_controls.sessionInput:SetProtected(true) - poesessid_controls.sessionInput.placeholder = "Enter your session ID here" - poesessid_controls.sessionInput.tooltipText = "You can get this from your web browser's cookies while logged into the Path of Exile website." - poesessid_controls.save = new("ButtonControl", {"TOPRIGHT", poesessid_controls.sessionInput, "TOP"}, {-8, 24, 90, row_height}, "Save", function() - main.POESESSID = poesessid_controls.sessionInput.buf - main:ClosePopup() - main:SaveSettings() - self:UpdateRealms() - end) - poesessid_controls.save.enabled = function() return #poesessid_controls.sessionInput.buf == 32 or poesessid_controls.sessionInput.buf == "" end - poesessid_controls.cancel = new("ButtonControl", {"TOPLEFT", poesessid_controls.sessionInput, "TOP"}, {8, 24, 90, row_height}, "Cancel", function() - main:ClosePopup() + self.loginStatus = function() + if main.api.authToken then + self.clickTime = nil + return "Authenticated" + elseif self.clickTime then + local left = m_max(0,(self.clickTime + 30) - os.time()) + if left == 0 then + self.clickTime = nil + return "Not authenticated" + else + return "Logging in... (" .. left .. ")" + end + else + return colorCodes.WARNING.."Not authenticated" + end + end + + if main.api.authToken then + main.api:ValidateAuth(function(valid, _updateSettings) + if valid then + return + else + main.api:ResetDetails() + end end) - main:OpenPopup(364, 72, "Change session ID", poesessid_controls) + end + self.controls.poesessidButton = new("ButtonControl", {"TOPLEFT", self.controls.setSelect, "TOPLEFT"}, {0, row_height + row_vertical_padding, 188, row_height}, self.loginStatus, function() + -- LOGIN + if not main.api.authToken then + main.api:FetchAuthToken(function() + if main.api.authToken then + self.loginStatus = "Authenticated" + + main.lastToken = main.api.authToken + main.lastRefreshToken = main.api.refreshToken + main.tokenExpiry = main.api.tokenExpiry + main:SaveSettings() + else + self.loginStatus = colorCodes.WARNING.."Not authenticated" + end + end) + self.clickTime = os.time() + -- LOGOUT + else + main.lastToken = nil + main.api.authToken = nil + main.lastRefreshToken = nil + main.api.refreshToken = nil + main.tokenExpiry = nil + main.api.tokenExpiry = nil + main:SaveSettings() + end end) self.controls.poesessidButton.tooltipText = [[ -The Trader feature supports two modes of operation depending on the POESESSID availability. -You can click this button to enter your POESESSID. +The Trader feature supports two modes of operation depending on the authorization availability. +You can click this button to authorize PoB by logging in. ^2Session Mode^7 -- Requires POESESSID. +- Requires authorization on pathofexile.com. - You can search, compare, and quickly import items without leaving Path of Building. +- You can select an item and search it directly. - You can generate and perform searches for the private leagues you are participating. ^xFF9922No Session Mode^7 -- Doesn't require POESESSID. +- Doesn't require authorization. - You cannot search and compare items in Path of Building. - You can generate weighted search URLs but have to visit the trade site and manually import items. - You can only generate weighted searches for public leagues. (Generated searches can be modified @@ -292,7 +355,7 @@ on trade site to work on other leagues and realms)]] "In person (online)", "Any (includes offline)" } - + self.controls.tradeTypeSelection = new("DropDownControl", { "TOPLEFT", self.controls.poesessidButton, "BOTTOMLEFT" }, { 0, row_vertical_padding, 188, row_height }, self.tradeTypes, function(index, value) self.tradeTypeIndex = index @@ -302,7 +365,7 @@ on trade site to work on other leagues and realms)]] -- Fetches Box self.maxFetchPerSearchDefault = 2 - self.controls.fetchCountEdit = new("EditControl", {"TOPRIGHT", nil, "TOPRIGHT"}, {-12, 19, 154, row_height}, "", "Fetch Pages", "%D", 3, function(buf) + self.controls.fetchCountEdit = new("EditControl", {"TOPRIGHT", nil, "TOPRIGHT"}, {-12, 19, 150, row_height}, "", "Fetch Pages", "%D", 3, function(buf) self.maxFetchPages = m_min(m_max(tonumber(buf) or self.maxFetchPerSearchDefault, 1), 10) self.tradeQueryRequests.maxFetchPerSearch = 10 * self.maxFetchPages self.controls.fetchCountEdit.focusValue = self.maxFetchPages @@ -464,7 +527,27 @@ Highest Weight - Displays the order retrieved from trade]] t_insert(slotTables, { slotName = self.itemsTab.sockets[nodeId].label, nodeId = nodeId }) end - self.controls.sectionAnchor = new("LabelControl", {"LEFT", self.controls.tradeTypeSelection, "LEFT"}, {0, row_vertical_padding + row_height, 0, 0}, "") + self.controls.authenticateButton = new("ButtonControl", {"TOPLEFT",self.controls.characterImportAnchor,"TOPLEFT"}, {0, 0, 200, 16}, "^7Authorize with Path of Exile", function() + main.api:FetchAuthToken(function() + if main.api.authToken then + self.charImportStatus = "Authenticated" + + main.lastToken = main.api.authToken + main.lastRefreshToken = main.api.refreshToken + main.tokenExpiry = main.api.tokenExpiry + main:SaveSettings() + else + self.charImportStatus = colorCodes.WARNING.."Not authenticated" + end + end) + local clickTime = os.time() + self.charImportStatus = function() return "Logging in... (" .. m_max(0, (clickTime + 30) - os.time()) .. ")" end + end) + self.controls.authenticateButton.shown = function() + return self.charImportMode == "AUTHENTICATION" + end + + self.controls.sectionAnchor = new("LabelControl", {"LEFT", self.controls.tradeTypeSelection, "LEFT"}, {0, row_vertical_padding, 0, 0}, "") top_pane_alignment_ref = {"TOPLEFT", self.controls.sectionAnchor, "TOPLEFT"} local scrollBarShown = #slotTables > 21 -- clipping starts beyond this -- dynamically hide rows that are above or below the scrollBar @@ -530,13 +613,17 @@ Highest Weight - Displays the order retrieved from trade]] main:ClosePopup() -- there's a case where if you have a socket(s) allocated, open TradeQuery, close it, dealloc, then open TradeQuery again -- the deallocated socket controls were still showing, so this will remove all dynamically created controls from items - wipeItemControls() + + -- later note: this is disabled because it causes the trader to crash if + -- it's closed mid-search + -- wipeItemControls() end) self.controls.updateCurrencyConversion = new("ButtonControl", {"BOTTOMLEFT", nil, "BOTTOMLEFT"}, {pane_margins_horizontal, -pane_margins_vertical, 240, row_height}, "Get Currency Conversion Rates", function() self:PullPoENinjaCurrencyConversion(self.pbLeague) end) self.controls.pbNotice = new("LabelControl", {"BOTTOMRIGHT", nil, "BOTTOMRIGHT"}, {-row_height - pane_margins_vertical - row_vertical_padding, -pane_margins_vertical, 300, row_height}, "") + self:SetCurrencyConversionButton() -- used in PopupDialog:Draw() local function scrollBarFunc() @@ -791,10 +878,10 @@ function TradeQueryClass:UpdateControlsWithItems(row_idx) local sortMode = self.itemSortSelectionList[self.pbItemSortSelectionIndex] local sortedItems, errMsg = self:SortFetchResults(row_idx, sortMode) if errMsg == "MissingConversionRates" then - self:SetNotice(self.controls.pbNotice, "^4Price sorting is not available, falling back to Stat Value sort.") + self:SetNotice(self.controls.pbNotice, "^4Please update currency rates to sort by price. Falling back to Stat Value sort.") sortedItems, errMsg = self:SortFetchResults(row_idx, self.sortModes.StatValue) - end - if errMsg then + return + elseif errMsg then self:SetNotice(self.controls.pbNotice, "Error: " .. errMsg) return else @@ -837,19 +924,20 @@ function TradeQueryClass:SortFetchResults(row_idx, mode) end return sum end + --- @return table? local function getPriceTable() - local out = {} - local pricedItems = self:addChaosEquivalentPriceToItems(self.resultTbl[row_idx]) - if pricedItems == nil then - return nil - end - for index, tbl in pairs(pricedItems) do - local chaosAmount = self:ConvertCurrencyToChaos(tbl.currency, tbl.amount) - if chaosAmount > 0 then - out[index] = chaosAmount - end + --- @type table + local divPrices = {} + for idx, item in ipairs(self.resultTbl[row_idx]) do + if item.currency and item.amount then + local divs = self:ConvertCurrencyToDivs(item.currency, item.amount) + if not divs then + return nil + end + divPrices[idx] = divs + else return nil end end - return out + return divPrices end local newTbl = {} if mode == self.sortModes.Weight then @@ -859,7 +947,6 @@ function TradeQueryClass:SortFetchResults(row_idx, mode) return newTbl elseif mode == self.sortModes.StatValue then for result_index = 1, #self.resultTbl[row_idx] do - --ConPrintf("%.3f", getResultWeight(result_index)) t_insert(newTbl, { outputAttr = getResultWeight(result_index), index = result_index }) end table.sort(newTbl, function(a,b) return a.outputAttr > b.outputAttr end) @@ -879,7 +966,7 @@ function TradeQueryClass:SortFetchResults(row_idx, mode) -- still seems to overrate very cheap items that are bad -- scaling factor for price - local k = 0.03 + local k = 0.1 t_insert(newTbl, { outputAttr = getResultWeight(result_index) - k * math.log(priceTable[result_index], 10), index = result_index }) @@ -900,19 +987,6 @@ function TradeQueryClass:SortFetchResults(row_idx, mode) return newTbl end ---- Convert item prices to chaos equivalent using poeninja data, returns nil if fails to convert any -function TradeQueryClass:addChaosEquivalentPriceToItems(items) - local outputItems = copyTable(items) - for _, item in ipairs(outputItems) do - local chaosAmount = self:ConvertCurrencyToChaos(item.currency, item.amount) - if chaosAmount == nil then - return nil - end - item.chaosEquivalent = chaosAmount - end - return outputItems -end - -- return valid slot for Watcher's Eye -- Tries to first return an existing watcher's eye slot if possible function TradeQueryClass:findValidSlotForWatchersEye() @@ -935,7 +1009,6 @@ function TradeQueryClass:findValidSlotForWatchersEye() end end end - -- Method to generate pane elements for each item slot function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, row_vertical_padding, row_height) local controls = self.controls @@ -955,8 +1028,8 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro else self:SetNotice(context.controls.pbNotice, "") end - if main.POESESSID == nil or main.POESESSID == "" then - local url = self.tradeQueryRequests:buildUrl(self.hostName .. "trade/search", self.pbRealm, self.pbLeague) + if main.api.authToken == nil then + local url = self.tradeQueryRequests:buildUrl(self.hostName .. "trade2/search", self.pbRealm, self.pbLeague) url = url .. "?q=" .. urlEncode(query) controls["uri"..context.row_idx]:SetText(url, true) return @@ -1006,7 +1079,10 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro end) controls["bestButton"..row_idx].shown = function() return not self.resultTbl[row_idx] end controls["bestButton"..row_idx].enabled = function() return self.pbLeague end - controls["bestButton"..row_idx].tooltipText = "Creates a weighted search to find the highest Stat Value items for this slot." + controls["bestButton"..row_idx].tooltipText = [[Creates a weighted search to find the highest Stat Value items for this slot. +Note that even if you are authenticated, you can click this button again to show the search link. +If you have additional requirements that the trade tool doesn't cover (e.g. Adorned Magic jewels), +you can add them, copy the link here, and press "Price Item" to evaluate the items.]] local pbURL controls["uri"..row_idx] = new("EditControl", { "TOPLEFT", controls["bestButton"..row_idx], "TOPRIGHT"}, {8, 0, 514, row_height}, nil, nil, "^%C\t\n", nil, function(buf) local subpath = buf:match(self.hostName .. "trade/search/(.+)$") or "" @@ -1051,15 +1127,15 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro end) end) controls["priceButton"..row_idx].enabled = function() - local poesessidAvailable = main.POESESSID and main.POESESSID ~= "" + local isAuthorized = main.api.authToken ~= nil local validURL = controls["uri"..row_idx].validURL local isSearching = controls["priceButton"..row_idx].label == "Searching..." - return poesessidAvailable and validURL and not isSearching + return isAuthorized and validURL and not isSearching end controls["priceButton"..row_idx].tooltipFunc = function(tooltip) tooltip:Clear() - if not main.POESESSID or main.POESESSID == "" then - tooltip:AddLine(16, "You must set your POESESSID to use search feature") + if not main.api.authToken then + tooltip:AddLine(16, "You must log in to use the search feature") elseif not controls["uri"..row_idx].validURL then tooltip:AddLine(16, "Enter a valid trade URL") end @@ -1162,7 +1238,9 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro local price = self.totalPrice[row_idx] and self.totalPrice[row_idx].amount .. " " .. self.totalPrice[row_idx].currency - if itemResult.whisper then + -- we also check the price type so we can prefer instant buyout over + -- whisper + if itemResult.whisper and (itemResult.priceType ~= "~b/o") then return price and "Whisper for " .. price or "Whisper" else return price and "Search for " .. price or "Search" @@ -1170,7 +1248,7 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro end, function() local itemResult = self.itemIndexTbl[row_idx] and self.resultTbl[row_idx][self.itemIndexTbl[row_idx]] - if itemResult.whisper then + if itemResult.whisper and (itemResult.priceType ~= "~b/o") then Copy(itemResult.whisper) else local exactQuery = dkjson.decode(self.lastQueries[row_idx]) @@ -1186,13 +1264,11 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro exactQuery.query.filters.trade_filters.filters.account = { input = itemResult.trader } local exactQueryStr = dkjson.encode(exactQuery) - - self.tradeQueryRequests:SearchWithQuery(self.pbRealm, self.pbLeague, exactQueryStr, function(_, _) - end, {callbackQueryId = function(queryId) - local url = self.hostName.."trade/search/"..self.pbLeague.."/"..queryId - Copy(url) - OpenURL(url) - end}) + + local encodedUrl = s_format("https://www.pathofexile.com/trade/search/%s?q=%s", self.pbLeague, urlEncode(exactQueryStr)) + + Copy(encodedUrl) + OpenURL(encodedUrl) end end) @@ -1205,6 +1281,7 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro tooltip:AddLine(16, text) end end + -- Method to update the Total Price string sum of all items function TradeQueryClass:GetTotalPriceString() local text = "" @@ -1244,38 +1321,26 @@ function TradeQueryClass:UpdateRealms() self.controls.realm:SetSel(self.pbRealmIndex) end - if main.POESESSID and main.POESESSID ~= "" then - -- Fetch from trade page using POESESSID, includes private leagues - ConPrintf("Fetching realms and leagues using POESESSID") - self.tradeQueryRequests:FetchRealmsAndLeaguesHTML(function(data, errMsg) + -- use trade leagues api to get trade leagues including private leagues is valid. + for _, realmId in pairs (self.realmIds) do + self.tradeQueryRequests:FetchLeagues(realmId, function(leagues, errMsg) if errMsg then - self:SetNotice(self.controls.pbNotice, "Error while fetching league list: "..errMsg) - return + self:SetNotice(self.controls.pbNotice, "Using Fallback Error while fetching league list: "..errMsg) end - local leagues = data.leagues self.allLeagues = {} - for _, value in ipairs(leagues) do - if not self.allLeagues[value.realm] then self.allLeagues[value.realm] = {} end - t_insert(self.allLeagues[value.realm], value.id) - end - self.realmIds = {} - for _, value in pairs(data.realms) do - -- filter out only Path of Exile one realms, as poe2 is not supported yet - if value.text:match("PoE 1 ") then - self.realmIds[value.text:gsub("PoE 1 ", "")] = value.id - end + for _, league in ipairs(leagues) do + if not self.allLeagues[realmId] then self.allLeagues[realmId] = {} end + t_insert(self.allLeagues[realmId], league) end setRealmDropList() end) - else - -- Fallback to static list - ConPrintf("Using static realms list") - self.realmIds = { - ["PC"] = "pc", - ["PS4"] = "sony", - ["Xbox"] = "xbox", - } - setRealmDropList() end + + -- perform a generic search to make sure the authorization is valid. + self.tradeQueryRequests:PerformSearch("pc", "Standard", [[{"query":{"status":{"option":"online"},"stats":[{"type":"and","filters":[]}]},"sort":{"price":"asc"}}]], function(response, errMsg) + if errMsg then + self:SetNotice(self.controls.pbNotice, "Error: " .. tostring(errMsg)) + end + end) end diff --git a/src/Classes/TradeQueryRateLimiter.lua b/src/Classes/TradeQueryRateLimiter.lua index 42573d0f3b..08cf07afec 100644 --- a/src/Classes/TradeQueryRateLimiter.lua +++ b/src/Classes/TradeQueryRateLimiter.lua @@ -8,30 +8,30 @@ ---@class TradeQueryRateLimiter local TradeQueryRateLimiterClass = newClass("TradeQueryRateLimiter", function(self) -- policies_sample = { - -- -- label: policy - -- ["trade-search-request-limit"] = { - -- -- label: rule - -- ["Ip"] = { - -- ["state"] = { - -- ["60"] = {["timeout"] = 0, ["request"] = 1}, - -- ["300"] = {["timeout"] = 0, ["request"] = 1}, - -- ["10"] = {["timeout"] = 0, ["request"] = 1} - -- }, - -- ["limits"] = { - -- ["60"] = {["timeout"] = 120, ["request"] = 15}, - -- ["300"] = {["timeout"] = 1800, ["request"] = 60}, - -- ["10"] = {["timeout"] = 60, ["request"] = 8} - -- } - -- }, - -- ["Account"] = { - -- ["state"] = { - -- ["5"] = {["timeout"] = 0, ["request"] = 1} - -- }, - -- ["limits"] = { - -- ["5"] = {["timeout"] = 60, ["request"] = 3} - -- } - -- } - -- } + -- -- label: policy + -- ["trade-search-request-limit"] = { + -- -- label: rule + -- ["Ip"] = { + -- ["state"] = { + -- ["60"] = {["timeout"] = 0, ["request"] = 1}, + -- ["300"] = {["timeout"] = 0, ["request"] = 1}, + -- ["10"] = {["timeout"] = 0, ["request"] = 1} + -- }, + -- ["limits"] = { + -- ["60"] = {["timeout"] = 120, ["request"] = 15}, + -- ["300"] = {["timeout"] = 1800, ["request"] = 60}, + -- ["10"] = {["timeout"] = 60, ["request"] = 8} + -- } + -- }, + -- ["Account"] = { + -- ["state"] = { + -- ["5"] = {["timeout"] = 0, ["request"] = 1} + -- }, + -- ["limits"] = { + -- ["5"] = {["timeout"] = 60, ["request"] = 3} + -- } + -- } + -- } -- } self.policies = {} self.retryAfter = {} @@ -52,7 +52,9 @@ local TradeQueryRateLimiterClass = newClass("TradeQueryRateLimiter", function(se -- state shows more requests than expected (external requests) self.pendingRequests = { ["trade-search-request-limit"] = {}, - ["trade-fetch-request-limit"] = {} + ["trade-fetch-request-limit"] = {}, + ["character-list-request-limit"] = {}, + ["character-request-limit"] = {} } end) @@ -69,10 +71,10 @@ function TradeQueryRateLimiterClass:ParseHeader(headerString) return headers end -function TradeQueryRateLimiterClass:ParsePolicy(headerString) +function TradeQueryRateLimiterClass:ParsePolicy(headerString, policy) local policies = {} local headers = self:ParseHeader(headerString) - local policyName = headers["x-rate-limit-policy"] + local policyName = headers["x-rate-limit-policy"] or policy policies[policyName] = {} local retryAfter = headers["retry-after"] if retryAfter then @@ -107,8 +109,11 @@ function TradeQueryRateLimiterClass:ParsePolicy(headerString) return policies end -function TradeQueryRateLimiterClass:UpdateFromHeader(headerString) - local newPolicies = self:ParsePolicy(headerString) +function TradeQueryRateLimiterClass:UpdateFromHeader(headerString, policy) + local newPolicies = self:ParsePolicy(headerString, policy) + if not newPolicies then + return + end for policyKey, policyValue in pairs(newPolicies) do if self.requestHistory[policyKey] == nil then self.requestHistory[policyKey] = { timestamps = {} } diff --git a/src/Classes/TradeQueryRequests.lua b/src/Classes/TradeQueryRequests.lua index 95f8b6a5ad..2b46a21f41 100644 --- a/src/Classes/TradeQueryRequests.lua +++ b/src/Classes/TradeQueryRequests.lua @@ -19,41 +19,50 @@ local TradeQueryRequestsClass = newClass("TradeQueryRequests", function(self, ra end) ---Main routine for processing request queue -function TradeQueryRequestsClass:ProcessQueue() +--- @param onRateLimit fun(integer)? +function TradeQueryRequestsClass:ProcessQueue(onRateLimit) for key, queue in pairs(self.requestQueue) do if #queue > 0 then local policy = self.rateLimiter:GetPolicyName(key) local now = os.time() local timeNext = self.rateLimiter:NextRequestTime(policy, now) + local timeLeft = timeNext - now + -- relay wait info to caller when actually waiting, and not just + -- getting a magic poe2 release date number + if onRateLimit and timeLeft > 1 and timeNext ~= 1956528000 then + onRateLimit(timeLeft) + end if not (queue[1].retryTime and now < queue[1].retryTime) then if now >= timeNext then local request = table.remove(queue, 1) local requestId = self.rateLimiter:InsertRequest(policy) local onComplete = function(response, errMsg) self.rateLimiter:FinishRequest(policy, requestId) - self.rateLimiter:UpdateFromHeader(response.header) + self.rateLimiter:UpdateFromHeader(response.header, policy) if response.header:match("HTTP/[%d%.]+ (%d+)") == "429" then + local retryAfter = response.header:match("Retry%-After:%s+(%d+)") + retryAfter = retryAfter and tonumber(retryAfter) or 0 request.attempts = (request.attempts or 0) + 1 - local backoff = m_min(2 ^ request.attempts, 60) + + local backoff = math.max(math.min(2 ^ request.attempts, 60), retryAfter) request.retryTime = os.time() + backoff table.insert(queue, 1, request) + -- optional callback with the backoff time when rate + -- limited to inform user + if onRateLimit then + onRateLimit(backoff) + end return end - -- if limit rules don't return account then the POESESSID is invalid. - if response.header:match("[xX]%-[rR]ate%-[lL]imit%-[rR]ules: (.-)\n"):match("Account") == nil and main.POESESSID ~= "" then - main.POESESSID = "" - if errMsg then - errMsg = errMsg .. "\nPOESESSID is invalid. Please Re-Log and reset" - else - errMsg = "POESESSID is invalid. Please Re-Log and reset" - end + if errMsg == "Response code: 401" and response.body:find("invalid_token") then + errMsg = errMsg .. "\nAuthorization is invalid. Please Re-Log and reset" + main.api:ResetDetails() end request.callback(response.body, errMsg, unpack(request.callbackParams or {})) end - -- self:SendRequest(request.url , onComplete, {body = request.body, poesessid = main.POESESSID}) local header = "Content-Type: application/json" - if main.POESESSID ~= "" then - header = header .. "\nCookie: POESESSID=" .. main.POESESSID + if main.api.authToken then + header = header .."\nAuthorization: Bearer "..main.api.authToken end launch:DownloadPage(request.url, onComplete, { header = header, @@ -190,6 +199,7 @@ end ---Perform search and run callback function on returned item hashes. ---Item info has to be fetched separately +---@param realm string ---@param league string ---@param query string ---@param callback fun(response:table, errMsg:string) @@ -214,17 +224,13 @@ function TradeQueryRequestsClass:PerformSearch(realm, league, query, callback) callback(response, errMsg) end if response.error.message:find("Logging in will increase this limit") then - if main.POESESSID ~= "" then - errMsg = "POESESSID is invalid. Please Re-Log and reset" - else - errMsg = "Session is invalid. Please add your POESESSID" - end + errMsg = "Authorization is invalid. Please Re-Log and reset" else -- Report unhandled error errMsg = "[ " .. response.error.code .. ": " .. response.error.message .. " ]" end else - ConPrintf("Found 0 results for " .. self.hostName .. "api/trade/search/" .. league .. "/" .. response.id) + ConPrintf("Found 0 results for %sapi/trade/search/%s/%s", self.hostName, league, response.id) errMsg = "No Matching Results Found" end return callback(response, errMsg) @@ -285,11 +291,7 @@ function TradeQueryRequestsClass:FetchResultBlock(url, callback) table.insert(items, { amount = trade_entry.listing.price.amount, currency = trade_entry.listing.price.currency, - -- note: using these to travel to the hideout or for a - -- direct whisper is not allowed, even if they are provided - -- right here - -- hideout_token = trade_entry.listing.hideout_token, - -- whisper_token = trade_entry.listing.whisper_token, + priceType = trade_entry.listing.price.type, item_string = common.base64.decode(trade_entry.item.extended.text), whisper = trade_entry.listing.whisper, trader = trade_entry.listing.account.name, @@ -302,7 +304,7 @@ function TradeQueryRequestsClass:FetchResultBlock(url, callback) }) end ----@param callback fun(items:table, errMsg:string, query:string) +---@param callback fun(items:table, errMsg:string, query: string?) function TradeQueryRequestsClass:SearchWithURL(url, callback) local subpath = url:match(self.hostName .. "trade/search/(.+)$") local paths = {} @@ -318,10 +320,24 @@ function TradeQueryRequestsClass:SearchWithURL(url, callback) end league = paths[#paths-1] queryId = paths[#paths] - self:FetchSearchQueryHTML(realm, league, queryId, function(query, errMsg) + self:FetchSearchQuery(realm, league, queryId, function(query, errMsg) if errMsg then return callback(nil, errMsg, nil) end + + -- update sorting on provided url to sort by weights. + local json_data = dkjson.decode(query) + if not json_data or json_data.error then + errMsg = json_data and json_data.error or "Failed to parse search query JSON" + end + if json_data.query.stats and json_data.query.stats[1] and json_data.query.stats[1].type == "weight" then + json_data.sort = {} + json_data.sort["statgroup.0"] = "desc" + else + json_data.sort = { price = "asc"} + end + query = dkjson.encode(json_data) + self:SearchWithQuery(realm, league, query, function(items, searchErrMsg) callback(items, searchErrMsg, query) end) @@ -349,117 +365,30 @@ function TradeQueryRequestsClass:FetchSearchQuery(realm, league, queryId, callba }) end ---- HTML parsing to circumvent extra API call for query fetching ---- queryId -> query fetching via Poe API call costs precious search requests ---- But the search page HTML also contains the query object and this request is not throttled ----@param queryId string ----@param callback fun(query:string, errMsg:string) ----@see TradeQueryRequests#FetchSearchQuery -function TradeQueryRequestsClass:FetchSearchQueryHTML(realm, league, queryId, callback) - if main.POESESSID == "" then - return callback(nil, "Please provide your POESESSID") - end - local header = "Cookie: POESESSID=" .. main.POESESSID - launch:DownloadPage(self:buildUrl(self.hostName .. "trade/search", realm, league, queryId), - function(response, errMsg) - if errMsg then - return callback(nil, errMsg) - end - -- check if response.header includes "Cache-Control: must-revalidate" which indicates an invalid session - if response.header:lower():match("cache%-control:.+must%-revalidate") then - return callback(nil, "Failed to get search query, check POESESSID") - end - -- full json state obj from HTML - local dataStr = response.body:match('require%(%["main"%].+ t%((.+)%);}%);}%);') - if not dataStr then - return callback(nil, "JSON object not found on the page.") - end - local data, _, err = dkjson.decode(dataStr) - if err then - return callback(nil, "Failed to parse JSON object. ".. err) - end - local query = { query = data.state } - if data.state.stats and data.state.stats[1] and data.state.stats[1].type == "weight" then - query.sort = {} - query.sort["statgroup.0"] = "desc" - else - query.sort = { price = "asc"} - end - query.query.status = { option = query.query.status} -- works either way? - local queryStr = dkjson.encode(query) - callback(queryStr, errMsg) - end, - {header = header}) -end - ---- Fetches the list of all available leagues using HTML parsing ---- This should get all leagues, including the ones that are not available through API ---- ---- example output: ---- result = { ---- leagues = [ ---- { ---- "id": "Sanctum", ---- "realm": "pc", ---- "text": "Sanctum" ---- }, ---- ], ---- realms = [ ---- { ---- "id": "sony", ---- "text": "PS4" ---- }, ---- ] ---- } ----@param callback fun(result:table, errMsg:string) -function TradeQueryRequestsClass:FetchRealmsAndLeaguesHTML(callback) - if main.POESESSID == "" then - return callback(nil, "Please provide your POESESSID") - end - local header = "Cookie: POESESSID=" .. main.POESESSID - launch:DownloadPage( - self.hostName .. "trade", - function(response, errMsg) - if errMsg then - return callback(nil, errMsg) - end - -- full json state obj from HTML - local dataStr = response.body:match('require%(%["main"%].+ t%((.+)%);}%);}%);') - if not dataStr then - return callback(nil, "JSON object not found on the page.") - end - local data, _, err = dkjson.decode(dataStr) - if err then - return callback(nil, "Failed to parse JSON object. ".. err) - end - callback({leagues = data.leagues, realms = data.realms}, errMsg) - end, - {header = header} - ) -end - ---- Fetches the list of all available leagues using poe API +--- Fetches the list of all available leagues using trade2 league API ---@param realm string ---@param callback fun(query:table, errMsg:string) function TradeQueryRequestsClass:FetchLeagues(realm, callback) + local header = "Authorization: Bearer ".. (main.api.authToken or "") launch:DownloadPage( - self.hostName .. "api/leagues?compact=1&realm=" .. realm, - function(response, errMsg) - if errMsg then - return callback(nil, errMsg) - end - local json_data = dkjson.decode(response.body) - if not json_data or json_data.error then - errMsg = json_data and json_data.error or "Failed to get leagues" - end - local leagues = {} - for _, value in pairs(json_data) do - if (not value.id:find("SSF") and not value.id:find("Solo")) then + self.hostName .. "api/trade/data/leagues", + function(response, errMsg) + if errMsg then + return callback({"Standard", "Hardcore"}, errMsg) + end + local json_data = dkjson.decode(response.body) + if not json_data or json_data.error then + errMsg = json_data and json_data.error or "Failed to parse trade leagues JSON" + end + local leagues = {} + for _, value in pairs(json_data.result) do + if value.realm == realm then table.insert(leagues, value.id) end end - callback(leagues, errMsg) - end + callback(leagues, errMsg) + end, + {header = header} ) end diff --git a/src/HeadlessWrapper.lua b/src/HeadlessWrapper.lua index 774f3ae214..c089a20274 100644 --- a/src/HeadlessWrapper.lua +++ b/src/HeadlessWrapper.lua @@ -3,6 +3,7 @@ -- It can be run using a standard lua interpreter, although LuaJIT is preferable + -- Callbacks local callbackTable = { } local mainObject @@ -209,11 +210,15 @@ function loadBuildFromXML(xmlText, name) mainObject.main:SetMode("BUILD", false, name or "", xmlText) runCallback("OnFrame") end -function loadBuildFromJSON(getItemsJSON, getPassiveSkillsJSON) +function loadBuildFromJSON(characterJSON) mainObject.main:SetMode("BUILD", false, "") runCallback("OnFrame") - local charData = build.importTab:ImportItemsAndSkills(getItemsJSON) - build.importTab:ImportPassiveTreeAndJewels(getPassiveSkillsJSON, charData) + -- characterJSON could, for example, be the response from the PoE API: + -- https://www.pathofexile.com/developer/docs/reference#characters-get + local dkjson = require "dkjson" + local input = dkjson.decode(characterJSON) + local charData = build.importTab:ImportItemsAndSkills(input) + build.importTab:ImportPassiveTreeAndJewels(input) -- You now have a build without a correct main skill selected, or any configuration options set -- Good luck! end diff --git a/src/LaunchServer.lua b/src/LaunchServer.lua new file mode 100644 index 0000000000..428bbf9270 --- /dev/null +++ b/src/LaunchServer.lua @@ -0,0 +1,220 @@ +-- Start a server +local url = ... +local luaSocket = require("socket") +local server = luaSocket.tcp4() +local function bindSocket() + local res, err + res, err = server:bind("localhost", 49082) or server:bind("localhost", 49083) or server:bind("localhost", 49084) + if not res then + server:close() + else + res, err = server:listen(1) + if not res then + server:close() + else + return server + end + end + return nil, err +end +assert(bindSocket()) +local host, port = server:getsockname() +ConPrintf("Server started on %s:%s", host, port) + +local redirect_uri = string.format( + "http://localhost:%d", port +) +ConPrintf("Redirect URI: %s", redirect_uri) +url = url .. string.format("&redirect_uri=%s", redirect_uri) + +local commonResponse = [[ +HTTP/1.1 200 OK +Content-Type: text/html + + + + + + + PoB - Authentication Complete + + + +
+
+]] + +local commonResponseEnd = [[ +
+
+ + +]] + +ConPrintf("Opening URL: %s", url) +OpenURL(url) + +--- Handle an incoming socket connection, to complete an OAuth redirect. +--- @param client table @The socket connection to handle, as returned by `server:accept()`. +--- @param attempt number @The number of attempts made to handle an incoming connection. This is used for logging +--- purposes, since spurious issues can be difficult to identify otherwise. +--- @return boolean shouldRetry @Whether we should wait for another connection. If false, we've successfully responded +--- to a HTTP request. Note that, for the purposes of this function, we don't care whether authorization was *granted*, +--- just that the process itself was completed and the user was redirected as intended. +--- @return string? code @The OAuth authorization code. This is exchanged for an access token and refresh token later. +--- @return string? state @The OAuth state string. This is a sentinel value used to ensure that a request hasn't been +--- forged. +function handleConnection(client, attempt) + local shouldRetry, code, state = true, nil, nil + + local request, err = client:receive("*l") + if err then + ConPrintf("Attempt %d to handle incoming connection failed: %s", attempt, err) + elseif request then + local response + local _, _, method, path, version = request:find("^(%S+)%s(%S+)%s(%S+)") + if method ~= "GET" then + ConPrintf( + "Attempt %d to handle incoming connection received an invalid HTTP request: non-GET method %s", + attempt, + method + ) + + return true + end + + local queryParams = {} + for k, v in path:gmatch("([^&=?]+)=([^&=?]+)") do + queryParams[k] = v:gsub("%%(%x%x)", function(hex) + return string.char(tonumber(hex, 16)) + end) + end + + if queryParams["code"] ~= nil then + response = commonResponse .. [[ +

PoB - Authentication Successful

+

✅ Your authentication is complete! You can now return to the app.

+ ]] .. commonResponseEnd + code = queryParams["code"] + state = queryParams["state"] + else + response = commonResponse .. [[ +

PoB - Authentication Failed

+

❌ Authentication failed. Please try again.

+ ]] .. queryParams["error"] .. ": " .. queryParams["error_description"] .. [[ + ]] .. commonResponseEnd + end + + shouldRetry = false + if attempt ~= 1 then + ConPrintf("Attempt %d to handle incoming connection received a valid HTTP request", attempt) + end + + -- Send HTTP Response + --ConPrintf("Sending response: %s", response) + client:send(response) + end + + return shouldRetry, code, state +end + +-- Misbehaving software (think VPNs, anything network-related, even OS services) will occasionally attempt to connect +-- to newly-opened sockets for one reason or another. Previously, PoB only waited for one connection, and gave up +-- immediately if something went wrong. +-- +-- This would result in a sequence of events roughly like this: +-- 1. PoB opens a socket +-- 2. A misbehaving piece of software connects to the socket, sends nothing, then terminates the connection +-- 3. PoB tries to read from the socket, receives an error since the connection is terminated, and closes the server +-- 4. OAuth authorization succeeds, but by the time the user is redirected back to PoB, the server is already closed +-- 5. PoB never receives the OAuth redirect, and doesn't have any of the information necessary to use the API +-- +-- To avoid this, we instead allow for any number of incoming connections, and simply stop listening for them once +-- either a) 30 seconds have elapsed or b) we've received a legitimate HTTP request and responded to it. +-- +-- Unfortunately, this still isn't perfect: in theory, two applications (such as a browser, and something else) could +-- attempt to establish a connection at the same time. In the future, this could be refactored to perform non-blocking +-- IO, so that it can operate concurrently, but hopefully that isn't necessary. +local attempt = 1 +local stopAt = os.time() + 30 +local errMsg +local shouldRetry, code, state = true, nil, nil +while (os.time() < stopAt) and shouldRetry do + -- `settimeout`` applies only to individual operations, but we're more concerned with not spending more than 30 + -- seconds *total* waiting, so we adjust with each iteration as necessary. + local remainingTime = math.max(0, stopAt - os.time()) + server:settimeout(remainingTime) + + local client = server:accept() + if not client then + goto retry + end + + client:settimeout(5) + shouldRetry, code, state = handleConnection(client, attempt) + client:close() + + :: retry :: + attempt = attempt + 1 +end +server:close() +if os.time() >= stopAt then + errMsg = "Timeout reached without a response received by the local server" +end +return code, errMsg, state, port diff --git a/src/Modules/Main.lua b/src/Modules/Main.lua index 3f6e19d74d..97689aa376 100644 --- a/src/Modules/Main.lua +++ b/src/Modules/Main.lua @@ -114,13 +114,12 @@ function main:Init() self.migrateEldritchImplicits = true self.notSupportedModTooltips = true self.notSupportedTooltipText = " ^8(Not supported in PoB yet)" - self.POESESSID = "" self.showPublicBuilds = true self.showFlavourText = true self.showAnimations = true self.showAllItemAffixes = true self.errorReadingSettings = false - + if not SetDPIScaleOverridePercent then SetDPIScaleOverridePercent = function(scale) end end if launch.devMode and IsKeyDown("CTRL") or os.getenv("REGENERATE_MOD_CACHE") == "1" then @@ -524,6 +523,9 @@ function main:LoadSettings(ignoreBuild) elseif node.elem == "Accounts" then self.lastAccountName = node.attrib.lastAccountName self.lastRealm = node.attrib.lastRealm + self.lastToken = node.attrib.lastToken + self.lastRefreshToken = node.attrib.lastRefreshToken + self.tokenExpiry = tonumber(node.attrib.tokenExpiry) for _, child in ipairs(node) do if child.elem == "Account" then self.gameAccounts[child.attrib.accountName] = { @@ -605,9 +607,6 @@ function main:LoadSettings(ignoreBuild) if node.attrib.notSupportedModTooltips then self.notSupportedModTooltips = node.attrib.notSupportedModTooltips == "true" end - if node.attrib.POESESSID then - self.POESESSID = node.attrib.POESESSID or "" - end if node.attrib.invertSliderScrollDirection then self.invertSliderScrollDirection = node.attrib.invertSliderScrollDirection == "true" end @@ -714,7 +713,7 @@ function main:SaveSettings() return true end t_insert(setXML, mode) - local accounts = { elem = "Accounts", attrib = { lastAccountName = self.lastAccountName, lastRealm = self.lastRealm } } + local accounts = { elem = "Accounts", attrib = { lastAccountName = self.lastAccountName, lastRealm = self.lastRealm, lastToken = self.lastToken, lastRefreshToken = self.lastRefreshToken, tokenExpiry = tostring(self.tokenExpiry) } } for accountName, account in pairs(self.gameAccounts) do t_insert(accounts, { elem = "Account", attrib = { accountName = accountName, sessionID = account.sessionID } }) end @@ -754,7 +753,6 @@ function main:SaveSettings() slotOnlyTooltips = tostring(self.slotOnlyTooltips), migrateEldritchImplicits = tostring(self.migrateEldritchImplicits), notSupportedModTooltips = tostring(self.notSupportedModTooltips), - POESESSID = self.POESESSID, invertSliderScrollDirection = tostring(self.invertSliderScrollDirection), disableDevAutoSave = tostring(self.disableDevAutoSave), showPublicBuilds = tostring(self.showPublicBuilds), @@ -968,7 +966,7 @@ function main:OpenOptionsPopup() controls.showAnimations = new("CheckBoxControl", { "TOPLEFT", nil, "TOPLEFT" }, { defaultLabelPlacementX, currentY, 20 }, "^7Show Animations:", function(state) self.showAnimations = state end) - + nextRow() controls.showAllItemAffixes = new("CheckBoxControl", { "TOPLEFT", nil, "TOPLEFT" }, { defaultLabelPlacementX, currentY, 20 }, "^7Show all item affixes sliders:", function(state) self.showAllItemAffixes = state