fix(bots): remove timestamp from verify_signature signing string

All 10 non-gatherer bots included timestamp in the request verification
signing string but the engine (auth.go SignRequest) does not include
timestamp. Every incoming turn request failed 401 verification, bots
crashed after 10 turns, and all matches ended in stalemate.

The engine documentation in auth.go is also misleading (old comment
mentioned timestamp in signing string) but the actual implementation
never included it. Fixed all language implementations to match.

Affected: random (py), swarm (ts), hunter (java), guardian (php),
          rusher (rs), assassin (rs), phalanx (rs), opportunist (go),
          farmer (go), scout (py), raider (java)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-05-02 20:43:44 -04:00
parent de4bc9eedd
commit 54548e4873
11 changed files with 11 additions and 11 deletions

View file

@ -118,7 +118,7 @@ fn verify_signature(
) -> bool {
let body_hash = sha2::Sha256::digest(body.as_bytes());
let body_hash_hex = hex::encode(body_hash);
let signing_string = format!("{}.{}.{}.{}", match_id, turn, timestamp, body_hash_hex);
let signing_string = format!("{}.{}.{}", match_id, turn, body_hash_hex);
let mut mac = match HmacSha256::new_from_slice(secret.as_bytes()) {
Ok(m) => m,

View file

@ -136,7 +136,7 @@ func handleHealth(w http.ResponseWriter, r *http.Request) {
func verifySignature(secret, matchID, turnStr, timestamp string, body []byte, signature string) bool {
bodyHash := sha256.Sum256(body)
signingString := fmt.Sprintf("%s.%s.%s.%s", matchID, turnStr, timestamp, hex.EncodeToString(bodyHash[:]))
signingString := fmt.Sprintf("%s.%s.%s", matchID, turnStr, hex.EncodeToString(bodyHash[:]))
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(signingString))
expected := hex.EncodeToString(mac.Sum(nil))

View file

@ -136,7 +136,7 @@ function handle_turn($conn, string $secret, GuardianStrategy $strategy, array $h
*/
function verify_signature(string $secret, string $matchId, string $turn, string $timestamp, string $body, string $signature): bool {
$bodyHash = hash('sha256', $body);
$signingString = "$matchId.$turn.$timestamp.$bodyHash";
$signingString = "$matchId.$turn.$bodyHash";
$expected = hash_hmac('sha256', $signingString, $secret);
return hash_equals($expected, $signature);
}

View file

@ -95,7 +95,7 @@ public class App {
String timestamp, String body, String signature) {
try {
String bodyHash = sha256Hex(body);
String signingString = matchId + "." + turn + "." + timestamp + "." + bodyHash;
String signingString = matchId + "." + turn + "." + bodyHash;
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");

View file

@ -136,7 +136,7 @@ func handleHealth(w http.ResponseWriter, r *http.Request) {
func verifySignature(secret, matchID, turnStr, timestamp string, body []byte, signature string) bool {
bodyHash := sha256.Sum256(body)
signingString := fmt.Sprintf("%s.%s.%s.%s", matchID, turnStr, timestamp, hex.EncodeToString(bodyHash[:]))
signingString := fmt.Sprintf("%s.%s.%s", matchID, turnStr, hex.EncodeToString(bodyHash[:]))
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(signingString))
expected := hex.EncodeToString(mac.Sum(nil))

View file

@ -120,7 +120,7 @@ fn verify_signature(
let body_hash = sha2::Sha256::digest(body.as_bytes());
let body_hash_hex = hex::encode(body_hash);
let signing_string = format!("{}.{}.{}.{}", match_id, turn, timestamp, body_hash_hex);
let signing_string = format!("{}.{}.{}", match_id, turn, body_hash_hex);
let mut mac = match HmacSha256::new_from_slice(secret.as_bytes()) {
Ok(m) => m,

View file

@ -86,7 +86,7 @@ public class App {
String timestamp, String body, String signature) {
try {
String bodyHash = sha256Hex(body);
String signingString = matchId + "." + turn + "." + timestamp + "." + bodyHash;
String signingString = matchId + "." + turn + "." + bodyHash;
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");

View file

@ -68,7 +68,7 @@ class RandomBotHandler(BaseHTTPRequestHandler):
timestamp: str, signature: str) -> bool:
"""Verify HMAC signature of incoming request."""
body_hash = hashlib.sha256(body).hexdigest()
signing_string = f"{match_id}.{turn}.{timestamp}.{body_hash}"
signing_string = f"{match_id}.{turn}.{body_hash}"
expected_sig = hmac.new(
self.secret.encode("utf-8"),
signing_string.encode("utf-8"),

View file

@ -133,7 +133,7 @@ fn verify_signature(
let body_hash = sha2::Sha256::digest(body.as_bytes());
let body_hash_hex = hex::encode(body_hash);
let signing_string = format!("{}.{}.{}.{}", match_id, turn, timestamp, body_hash_hex);
let signing_string = format!("{}.{}.{}", match_id, turn, body_hash_hex);
let mut mac = match HmacSha256::new_from_slice(secret.as_bytes()) {
Ok(m) => m,

View file

@ -330,7 +330,7 @@ class ScoutBotHandler(BaseHTTPRequestHandler):
def verify_signature(self, body: bytes, match_id: str, turn: str,
timestamp: str, signature: str) -> bool:
body_hash = hashlib.sha256(body).hexdigest()
signing_string = f"{match_id}.{turn}.{timestamp}.{body_hash}"
signing_string = f"{match_id}.{turn}.{body_hash}"
expected = hmac.new(
self.secret.encode("utf-8"),
signing_string.encode("utf-8"),

View file

@ -103,7 +103,7 @@ function verifySignature(
signature: string
): boolean {
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const signingString = `${matchId}.${turn}.${timestamp}.${bodyHash}`;
const signingString = `${matchId}.${turn}.${bodyHash}`;
const expected = crypto.createHmac('sha256', secret).update(signingString).digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}