$value) $lines[] = $name . '=' . $value; return implode("\n", $lines); } function garbo_read(array $fields, int $maxAgeSeconds): array { $fields['auth_date'] = (int) ($fields['auth_date'] ?? 0); $fields['user'] = isset($fields['user']) ? json_decode($fields['user'], true) : null; if ($maxAgeSeconds && time() - $fields['auth_date'] > $maxAgeSeconds) { throw new Exception('The launch data is too old.'); } return $fields; } function garbo_check_hash(string $initData, string $appSecret, int $maxAgeSeconds = 86400): array { $fields = garbo_fields($initData); $secretKey = hash_hmac('sha256', $appSecret, 'WebAppData', true); $expected = hash_hmac('sha256', garbo_check_string($fields, ['hash']), $secretKey); if (!hash_equals($expected, $fields['hash'] ?? '')) { throw new Exception("The launch data was not signed with this app's secret."); } return garbo_read($fields, $maxAgeSeconds); } function garbo_check_signature(string $initData, string $appId, int $maxAgeSeconds = 86400, string $publicKey = GARBO_PUBLIC_KEY): array { $fields = garbo_fields($initData); if (($fields['app_id'] ?? '') !== $appId) throw new Exception('The launch data is for another app.'); $signature = base64_decode(strtr($fields['signature'] ?? '', '-_', '+/')); $signed = $appId . ":WebAppData\n" . garbo_check_string($fields, ['hash', 'signature']); if (strlen($signature) !== SODIUM_CRYPTO_SIGN_BYTES || !sodium_crypto_sign_verify_detached($signature, $signed, sodium_hex2bin($publicKey))) { throw new Exception('The launch data was not signed by Garbo.'); } return garbo_read($fields, $maxAgeSeconds); }