sodium_bin2hex($sk), 'public_key_hex' => sodium_bin2hex($pk), ]; } /** * Encodage canonical d'un objet JSON pour la signature : on retire le champ * `signature` (s'il est là), on encode sans escapes inutiles, on signe ce blob. * Le client doit faire EXACTEMENT le même encodage avant verify. */ public static function canonicalJson(array $data): string { unset($data['signature']); return json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION ); } /** * Hash bcrypt-comparable d'une license_key (constant-time compare). */ public static function constantTimeEquals(string $a, string $b): bool { return hash_equals($a, $b); } /** * HMAC-SHA-256 hex pour les URLs présignées (v0.6). */ public static function hmacHex(string $message, string $secret): string { return hash_hmac('sha256', $message, $secret); } }