local HttpService = game:GetService("HttpService") local API_BASE = "https://vampauth.com/api" local PROJECT_ID = "your-project-id" local AUTH_SECRET = "your-48-hex-char-auth-secret" local Debug = false local function SetDebug(on) Debug = not not on end local function dbg(fmt, ...) if Debug then print("[vampauth] " .. string.format(fmt, ...)) end end local function MaskKey(k) if type(k) ~= "string" or #k <= 8 then return tostring(k) end return k:sub(1, 8) .. "..." end local function Truncate(s, n) s = tostring(s or "") if #s <= n then return s end return s:sub(1, n) .. "...(" .. #s .. " bytes)" end local function IdentifyExecutor() local ok, name = pcall(function() return (identifyexecutor()) end) if ok and type(name) == "string" and name ~= "" then return name end return "unknown" end local function HttpFetch(url, method, headers, body) local send = request or http_request or (syn and syn.request) if send then dbg("http: executor request global (%s)", IdentifyExecutor()) local ok, res = pcall(send, { Url = url, Method = method, Headers = headers, Body = body }) if ok and res then return res.StatusCode or res.statusCode or res.status_code or res.status or 0, res.Body or res.body or "" end dbg("http: executor request failed: %s", tostring(ok and "nil response" or tostring(res))) end dbg("http: falling back to HttpService:RequestAsync") local ok, res = pcall(function() return HttpService:RequestAsync({ Url = url, Method = method, Headers = headers, Body = body }) end) if not ok or not res then dbg("http: RequestAsync failed: %s", tostring(res)) return 0, "" end return res.StatusCode or res.statusCode or res.status_code or res.status or 0, res.Body or res.body or "" end local function JSONDecode(text) local ok, data = pcall(function() return HttpService:JSONDecode(text) end) if not ok then return nil end return data end local function JSONEncode(data) local ok, text = pcall(function() return HttpService:JSONEncode(data) end) if not ok then return "{}" end return text end local function NewNonce() return HttpService:GenerateGUID(false) .. HttpService:GenerateGUID(false) end local function AnalyticsClientId() local ok, id = pcall(function() return game:GetService("RbxAnalyticsService"):GetClientId() end) if ok and type(id) == "string" and id ~= "" then return id end end local function DefaultHWID() return AnalyticsClientId() end local H0, H1, H2, H3, H4 = 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 local function ToBytes(msg) local n = #msg local bytes = {} for i = 1, n do bytes[i] = string.byte(msg, i) end return bytes, n end local function SHA1(msg) local bytes, n = ToBytes(msg) bytes[n + 1] = 0x80 local pad = (64 - ((n + 1 + 8) % 64)) % 64 for i = n + 2, n + 1 + pad do bytes[i] = 0 end local bits = n * 8 local base = n + 1 + pad for i = 1, 8 do bytes[base + i] = math.floor(bits / 2 ^ ((8 - i) * 8)) % 256 end local h0, h1, h2, h3, h4 = H0, H1, H2, H3, H4 local w = {} for off = 0, base + 8 - 1, 64 do for i = 1, 16 do local j = off + (i - 1) * 4 w[i] = bytes[j + 1] * 0x1000000 + bytes[j + 2] * 0x10000 + bytes[j + 3] * 0x100 + bytes[j + 4] end for i = 17, 80 do local v = bit32.bxor(bit32.bxor(w[i - 3], w[i - 8]), bit32.bxor(w[i - 14], w[i - 16])) w[i] = bit32.lrotate(v, 1) end local a, b, c, d, e = h0, h1, h2, h3, h4 for i = 1, 80 do local f, k if i <= 20 then f = bit32.bxor(bit32.band(b, c), bit32.band(bit32.bnot(b), d)) k = 0x5A827999 elseif i <= 40 then f = bit32.bxor(bit32.bxor(b, c), d) k = 0x6ED9EBA1 elseif i <= 60 then f = bit32.bxor(bit32.bxor(bit32.band(b, c), bit32.band(b, d)), bit32.band(c, d)) k = 0x8F1BBCDC else f = bit32.bxor(bit32.bxor(b, c), d) k = 0xCA62C1D6 end local temp = bit32.band(bit32.lrotate(a, 5) + f + e + k + w[i], 0xFFFFFFFF) e = d d = c c = bit32.lrotate(b, 30) b = a a = temp end h0 = bit32.band(h0 + a, 0xFFFFFFFF) h1 = bit32.band(h1 + b, 0xFFFFFFFF) h2 = bit32.band(h2 + c, 0xFFFFFFFF) h3 = bit32.band(h3 + d, 0xFFFFFFFF) h4 = bit32.band(h4 + e, 0xFFFFFFFF) end return string.format("%08x%08x%08x%08x%08x", h0, h1, h2, h3, h4) end local function ToRawBytes(hex) local out = {} for i = 1, #hex, 2 do out[#out + 1] = string.char(tonumber(hex:sub(i, i), 16) * 16 + tonumber(hex:sub(i + 1, i + 1), 16)) end return table.concat(out) end local function Pack(bytes) local out = {} for i = 1, #bytes do out[i] = string.char(bytes[i]) end return table.concat(out) end local function HMACSha1Hex(secret, message) local key = secret if #key > 64 then key = ToRawBytes(SHA1(key)) end local keyBytes, keyLen = ToBytes(key) local ipad, opad = {}, {} for i = 1, 64 do local kb = i <= keyLen and keyBytes[i] or 0 ipad[i] = bit32.bxor(kb, 0x36) opad[i] = bit32.bxor(kb, 0x5C) end return SHA1(Pack(opad) .. ToRawBytes(SHA1(Pack(ipad) .. message))) end local function SignRequest(secret, parts) return HMACSha1Hex(secret, table.concat(parts, "|")) end local function ExpirySeconds(iso) if not iso then return 0 end local ok, dt = pcall(DateTime.fromIsoDate, iso) if not ok then return 0 end return math.floor(dt.UnixTimestampMillis / 1000) end local Vampauth = {} Vampauth.__index = Vampauth function Vampauth.new(cfg) cfg = cfg or {} return setmetatable({ baseUrl = cfg.baseUrl or API_BASE, projectId = cfg.projectId or PROJECT_ID, authSecret = cfg.authSecret or AUTH_SECRET, hwid = cfg.hwid or DefaultHWID(), debug = not not cfg.debug, state = nil, }, Vampauth) end function Vampauth:SetHWID(hwid) self.hwid = hwid end function Vampauth:SetDebug(on) self.debug = not not on end function Vampauth:State() return self.state end function Vampauth:Check(key, opts) opts = opts or {} self.state = nil local hwid = opts.hwid or self.hwid local nonce = NewNonce() SetDebug(self.debug) dbg("check: start (executor=%s, key=%s, hwid=%s)", IdentifyExecutor(), MaskKey(key), tostring(hwid)) if not self.projectId or self.projectId == "your-project-id" then return false, "config missing: set projectId" end if not self.authSecret or self.authSecret == "your-48-hex-char-auth-secret" then return false, "config missing: set authSecret" end local signature = SignRequest(self.authSecret, { nonce, key, hwid }) local executor = IdentifyExecutor() local url = self.baseUrl .. "/v1/key/check" dbg("request: POST %s", url) local payload = { project_id = self.projectId, key = key, hwid = hwid, nonce = nonce, signature = signature } if executor ~= "unknown" then payload.executor = executor end local status, text = HttpFetch( url, "POST", { ["Content-Type"] = "application/json" }, JSONEncode(payload) ) dbg("response: status=%s body=%s", tostring(status), Truncate(text, 200)) local function fail(reason) if self.debug then return false, reason .. " [status=" .. tostring(status) .. " body=" .. Truncate(text, 200) .. "]" end return false, reason end if status == 0 and text == "" then return fail("request failed") end local data = JSONDecode(text) if status < 200 or status >= 300 then local reason = (data and data.error) or ("HTTP " .. tostring(status)) if self.debug then reason = reason .. " [status=" .. tostring(status) .. " body=" .. Truncate(text, 200) .. "]" end return false, reason end if not data or data.nonce_echo ~= nonce or not data.signature or not data.project_id then local why = not data and "unparseable body" or data.nonce_echo ~= nonce and "nonce echo mismatch" or not data.signature and "missing signature" or "missing project_id" dbg("bad response: %s", why) return fail("bad response") end local okVerify, valid = pcall(function() local expected = HMACSha1Hex(self.authSecret, nonce .. "|" .. tostring(data.status) .. "|" .. tostring(ExpirySeconds(data.expires_at)) .. "|" .. data.project_id) return expected == data.signature end) if not okVerify or not valid then dbg("verify: %s", okVerify and "signature invalid" or ("verify error: " .. tostring(valid))) return fail("signature invalid") end dbg("verify: signature ok") if data.expires_at then local okdt, dt = pcall(DateTime.fromIsoDate, data.expires_at) if okdt and dt.UnixTimestamp <= os.time() then dbg("expired: %s <= %s", tostring(dt.UnixTimestamp), tostring(os.time())) return fail("expired") end end dbg("check: PASSED (expires_at=%s)", tostring(data.expires_at)) self.state = { status = data.status, key = key, hwid_bound = data.hwid_bound, expires_at = data.expires_at, created_at = data.created_at, discord_id = data.discord_id, discord_username = data.discord_username, key_group = type(data.group) == "string" and data.group or nil, reason = "Key validation", } return true, data end return Vampauth