Smurf & ban-evasion control at signup
Kernel anti-cheat (Vanguard, EAC, BattlEye) watches the running game; it doesn't stop a banned player making a fresh account on the same machine. Bind the account to the device at signup: a hardware identity is far harder to spoof than a disk-serial HWID, and it works before the game even launches.
Root Herald is the identity layer underneath your anti-cheat, not a replacement. Anti-cheat answers "is the running process clean?". Root Herald answers "is this the same device we banned last week?" and "is this one device or 40 smurf accounts on one device?".
Mint a challenge and collect evidence in the launcher
On account creation your matchmaker mints a single-use challenge with your rh_sk_ secret and relays the nonce. The launcher quotes the local TPM over it and posts the opaque evidence blob back — no keys on the client.
import { RootHerald } from "@rootherald/node";
const rh = new RootHerald({ secretKey: process.env.RH_SECRET_KEY! }); // rh_sk_… (backend only)
app.post("/signup/challenge", async (_req, res) => {
const { challengeId, nonce } = await rh.issueChallenge();
res.json({ challengeId, nonce });
});Collect on desktop with the native SDK (see the embedded game SDK guide); the launcher hands its opaque evidence blob to /signup with the rest of the form.
Verify server→server, then check the ban list and per-device cap
Appraise with rh.verify() and get the verdict back directly. Use the stable device.ueid against your ban list and your per-device account cap, then store it against the new account.
app.post("/signup", async (req, res) => {
const { challengeId, evidence, ...input } = req.body;
const verdict = await rh.verify(evidence, {
challengeId,
policy: "rootherald:builtin:strict-hardware",
});
if (verdict.device.verdict !== "pass") return forbid(res, "device_check_failed");
const ueid = verdict.device.ueid; // stable per-tenant device id
if (await banList.hasDevice(ueid)) return forbid(res, "device_banned");
const count = await accounts.countByDevice(ueid);
if (count >= MAX_ACCOUNTS_PER_DEVICE) return forbid(res, "too_many_accounts_on_device");
await accounts.create({ ...input, deviceId: ueid });
res.json({ ok: true });
});Optionally re-run the challenge → verify flow on launch and on ranked-match join to catch device swaps mid-life.
What comes back
The verdict is synchronous. Branch on device.verdict; key bans and caps off device.ueid.
{
"device": {
"ueid": "d41c…", // ban / cap key
"verdict": "pass", // pass | warn | fail
"earStatus": "affirming",
"attestationType": "tpm20"
}
}Pick the policy for your platforms
Use rootherald:builtin:strict-hardware-permissive-mobileif you ship on mobile: it accepts Android's Trusted Execution Environment (TEE — a secure area of the main processor) as well as its stronger StrongBox variant, and macOS Secure Enclave, so legitimate phones aren't false-positived, while still rejecting emulators and cloud vTPMs (the BlueStacks-farm and cloud-gaming-VM cases). Desktop-only? The default strict-hardware is right.
emulated or mobile-android-software is exactly the smurf-farm signature. Under a strict policy it fails; under a warn-tolerant policy it comes back with verdict: "warn" so you can shadow-ban or queue for review instead of hard-blocking.