feat: add TurnToken HMAC for match-action anti-cheat

This commit is contained in:
Keith Solomon
2026-07-25 22:39:29 -05:00
parent a0dcff2c2a
commit 3d8c1cfdb2
2 changed files with 167 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace BattleForge\Application;
use InvalidArgumentException;
final class TurnToken
{
private const MATCH_ID_PATTERN = '/^[a-f0-9]{16,}$/';
public static function issue(
string $secret,
string $matchId,
string $activeTeamId,
int $round,
int $lastActionIndex
): string {
self::assertValidInputs($matchId, $activeTeamId, $round, $lastActionIndex);
$payload = self::payload($matchId, $activeTeamId, $round, $lastActionIndex);
return substr(hash_hmac('sha256', $payload, $secret), 0, 32);
}
public static function verify(
string $secret,
string $matchId,
string $activeTeamId,
int $round,
int $lastActionIndex,
string $token
): bool {
if (
$token === ''
|| !self::isValidMatchId($matchId)
|| $activeTeamId === ''
|| $round < 1
|| $lastActionIndex < 0
) {
return false;
}
$expected = self::issue($secret, $matchId, $activeTeamId, $round, $lastActionIndex);
return hash_equals($expected, $token);
}
private static function assertValidInputs(
string $matchId,
string $activeTeamId,
int $round,
int $lastActionIndex
): void {
if (!self::isValidMatchId($matchId)) {
throw new InvalidArgumentException('Match id must be 16+ lowercase hex characters.');
}
if ($activeTeamId === '') {
throw new InvalidArgumentException('Active team id cannot be empty.');
}
if ($round < 1) {
throw new InvalidArgumentException('Round must be at least 1.');
}
if ($lastActionIndex < 0) {
throw new InvalidArgumentException('Last action index cannot be negative.');
}
}
private static function isValidMatchId(string $matchId): bool
{
return preg_match(self::MATCH_ID_PATTERN, $matchId) === 1;
}
private static function payload(string $matchId, string $activeTeamId, int $round, int $lastActionIndex): string
{
return implode('|', [$matchId, $activeTeamId, (string) $round, (string) $lastActionIndex]);
}
}