Przeglądaj źródła

feat(web): vless encryption new modes (#5517)

* feat(web): add vless encryption new modes

* feat(web): add translations for vless encryption modes

* feat(translation): bring "vlessAuthX25519" and "vlessAuthMlkem768" to general form
FunLay123 13 godzin temu
rodzic
commit
3ba43bd86d

+ 19 - 3
frontend/src/pages/inbounds/form/InboundFormModal.tsx

@@ -285,8 +285,12 @@ export default function InboundFormModal({
   ) => {
     if (block?.id === authId) return true;
     const label = (block?.label || '').toLowerCase().replace(/[-_\s]/g, '');
-    if (authId === 'mlkem768') return label.includes('mlkem768');
-    if (authId === 'x25519') return label.includes('x25519');
+    if (authId === 'mlkem768') return label.includes('mlkem768') && !label.includes('xorpub') && !label.includes('random');
+    if (authId === 'x25519') return label.includes('x25519') && !label.includes('xorpub') && !label.includes('random');
+    if (authId === 'mlkem768_xorpub') return label.includes('mlkem768') && label.includes('xorpub');
+    if (authId === 'mlkem768_random') return label.includes('mlkem768') && label.includes('random');
+    if (authId === 'x25519_xorpub') return label.includes('x25519') && label.includes('xorpub');
+    if (authId === 'x25519_random') return label.includes('x25519') && label.includes('random');
     return false;
   };
 
@@ -319,7 +323,19 @@ export default function InboundFormModal({
     const parts = enc.split('.').filter(Boolean);
     const authKey = parts[parts.length - 1] || '';
     if (!authKey) return t('pages.inbounds.vlessAuthCustom');
-    return authKey.length > 300
+    const mode = parts[1] || 'native';
+    const keyType = authKey.length > 300 ? 'mlkem768' : 'x25519';
+    if (mode === 'xorpub') {
+      return keyType === 'mlkem768'
+        ? t('pages.inbounds.vlessAuthMlkem768Xorpub')
+        : t('pages.inbounds.vlessAuthX25519Xorpub');
+    }
+    if (mode === 'random') {
+      return keyType === 'mlkem768'
+        ? t('pages.inbounds.vlessAuthMlkem768Random')
+        : t('pages.inbounds.vlessAuthX25519Random');
+    }
+    return keyType === 'mlkem768'
       ? t('pages.inbounds.vlessAuthMlkem768')
       : t('pages.inbounds.vlessAuthX25519');
   })();

+ 31 - 8
frontend/src/pages/inbounds/form/protocols/vless.tsx

@@ -1,12 +1,21 @@
+import { useState } from 'react';
 import { useTranslation } from 'react-i18next';
-import { Button, Form, Input, InputNumber, Space, Typography } from 'antd';
+import { Button, Form, Input, InputNumber, Select, Space, Typography } from 'antd';
+
+type VlessAuthKind =
+  | 'x25519'
+  | 'x25519_xorpub'
+  | 'x25519_random'
+  | 'mlkem768'
+  | 'mlkem768_xorpub'
+  | 'mlkem768_random';
 
 interface VlessFieldsProps {
   saving: boolean;
   selectedVlessAuth: string;
   network: string;
   security: string;
-  getNewVlessEnc: (kind: 'x25519' | 'mlkem768') => void;
+  getNewVlessEnc: (kind: VlessAuthKind) => void;
   clearVlessEnc: () => void;
 }
 
@@ -19,6 +28,17 @@ export default function VlessFields({
   clearVlessEnc,
 }: VlessFieldsProps) {
   const { t } = useTranslation();
+  const [authKind, setAuthKind] = useState<VlessAuthKind>('x25519');
+
+  const authOptions = [
+    { value: 'x25519', label: t('pages.inbounds.vlessAuthX25519') },
+    { value: 'x25519_xorpub', label: t('pages.inbounds.vlessAuthX25519Xorpub') },
+    { value: 'x25519_random', label: t('pages.inbounds.vlessAuthX25519Random') },
+    { value: 'mlkem768', label: t('pages.inbounds.vlessAuthMlkem768') },
+    { value: 'mlkem768_xorpub', label: t('pages.inbounds.vlessAuthMlkem768Xorpub') },
+    { value: 'mlkem768_random', label: t('pages.inbounds.vlessAuthMlkem768Random') },
+  ];
+
   return (
     <>
       <Form.Item name={['settings', 'decryption']} label={t('pages.inbounds.decryption')}>
@@ -27,13 +47,16 @@ export default function VlessFields({
       <Form.Item name={['settings', 'encryption']} label={t('pages.inbounds.encryption')}>
         <Input />
       </Form.Item>
-      <Form.Item label=" ">
+      <Form.Item label={t('pages.inbounds.vlessAuthGenerate')}>
         <Space size={8} wrap>
-          <Button type="primary" loading={saving} onClick={() => getNewVlessEnc('x25519')}>
-            {t('pages.inbounds.vlessAuthX25519')}
-          </Button>
-          <Button type="primary" loading={saving} onClick={() => getNewVlessEnc('mlkem768')}>
-            {t('pages.inbounds.vlessAuthMlkem768')}
+          <Select
+            value={authKind}
+            onChange={(v) => setAuthKind(v)}
+            options={authOptions}
+            style={{ width: 240 }}
+          />
+          <Button type="primary" loading={saving} onClick={() => getNewVlessEnc(authKind)}>
+            {t('pages.inbounds.vlessAuthGenerateButton')}
           </Button>
           <Button danger onClick={clearVlessEnc}>{t('clear')}</Button>
         </Space>

+ 24 - 1
internal/web/service/server.go

@@ -2064,11 +2064,34 @@ func (s *ServerService) GetNewVlessEnc() (any, error) {
 		return nil, err
 	}
 
+	auths := parseVlessEncAuths(out.String())
+	auths = append(auths, deriveVlessEncModes(auths)...)
+
 	return map[string]any{
-		"auths": parseVlessEncAuths(out.String()),
+		"auths": auths,
 	}, nil
 }
 
+func deriveVlessEncModes(auths []map[string]string) []map[string]string {
+	var extra []map[string]string
+	for _, a := range auths {
+		for _, mode := range []string{"xorpub", "random"} {
+			dec := strings.Replace(a["decryption"], ".native.", "."+mode+".", 1)
+			enc := strings.Replace(a["encryption"], ".native.", "."+mode+".", 1)
+			if dec == a["decryption"] && enc == a["encryption"] {
+				continue
+			}
+			extra = append(extra, map[string]string{
+				"id":         a["id"] + "_" + mode,
+				"label":      a["label"] + " (" + mode + ")",
+				"decryption": dec,
+				"encryption": enc,
+			})
+		}
+	}
+	return extra
+}
+
 func parseVlessEncAuths(output string) []map[string]string {
 	lines := strings.Split(output, "\n")
 	var auths []map[string]string

+ 28 - 0
internal/web/service/server_vlessenc_test.go

@@ -80,3 +80,31 @@ Authentication: X25519, not Post-Quantum
 		t.Fatalf("encryption = %q, want client", auths[0]["encryption"])
 	}
 }
+
+func TestDeriveVlessEncModes(t *testing.T) {
+	base := []map[string]string{
+		{
+			"id":         "x25519",
+			"label":      "X25519, not Post-Quantum",
+			"decryption": "mlkem768x25519plus.native.600s.server-key",
+			"encryption": "mlkem768x25519plus.native.0rtt.client-key",
+		},
+	}
+
+	derived := deriveVlessEncModes(base)
+	if len(derived) != 2 {
+		t.Fatalf("expected 2 derived blocks, got %d", len(derived))
+	}
+	if derived[0]["id"] != "x25519_xorpub" {
+		t.Errorf("id = %q, want x25519_xorpub", derived[0]["id"])
+	}
+	if derived[0]["decryption"] != "mlkem768x25519plus.xorpub.600s.server-key" {
+		t.Errorf("decryption = %q", derived[0]["decryption"])
+	}
+	if derived[0]["encryption"] != "mlkem768x25519plus.xorpub.0rtt.client-key" {
+		t.Errorf("encryption = %q", derived[0]["encryption"])
+	}
+	if derived[1]["id"] != "x25519_random" {
+		t.Errorf("id = %q, want x25519_random", derived[1]["id"])
+	}
+}

+ 9 - 3
internal/web/translation/ar-EG.json

@@ -407,10 +407,16 @@
       "sniffingDomainsExcluded": "النطاقات المستثناة",
       "decryption": "فك التشفير",
       "encryption": "التشفير",
-      "vlessAuthX25519": "مصادقة X25519",
-      "vlessAuthMlkem768": "مصادقة ML-KEM-768",
+      "vlessAuthX25519": "X25519 (native)",
+      "vlessAuthMlkem768": "ML-KEM-768 (native)",
+      "vlessAuthX25519Xorpub": "X25519 (xorpub)",
+      "vlessAuthX25519Random": "X25519 (random)",
+      "vlessAuthMlkem768Xorpub": "ML-KEM-768 (xorpub)",
+      "vlessAuthMlkem768Random": "ML-KEM-768 (random)",
       "vlessAuthCustom": "مخصص",
       "vlessAuthSelected": "المحدد: {auth}",
+      "vlessAuthGenerate": "إنشاء المفاتيح",
+      "vlessAuthGenerateButton": "إنشاء",
       "advanced": {
         "title": "أقسام JSON للاتصال الوارد",
         "subtitle": "JSON الكامل للاتصال الوارد ومحررات مخصصة لـ settings و sniffing و streamSettings.",
@@ -2071,4 +2077,4 @@
     "statusDown": "غير متصل",
     "statusUp": "متصل"
   }
-}
+}

+ 9 - 3
internal/web/translation/en-US.json

@@ -407,10 +407,16 @@
       "sniffingDomainsExcluded": "Domains excluded",
       "decryption": "Decryption",
       "encryption": "Encryption",
-      "vlessAuthX25519": "X25519 auth",
-      "vlessAuthMlkem768": "ML-KEM-768 auth",
+      "vlessAuthX25519": "X25519 (native)",
+      "vlessAuthMlkem768": "ML-KEM-768 (native)",
+      "vlessAuthX25519Xorpub": "X25519 (xorpub)",
+      "vlessAuthX25519Random": "X25519 (random)",
+      "vlessAuthMlkem768Xorpub": "ML-KEM-768 (xorpub)",
+      "vlessAuthMlkem768Random": "ML-KEM-768 (random)",
       "vlessAuthCustom": "Custom",
       "vlessAuthSelected": "Selected: {auth}",
+      "vlessAuthGenerate": "Generate keys",
+      "vlessAuthGenerateButton": "Generate",
       "advanced": {
         "title": "Inbound JSON sections",
         "subtitle": "Full inbound JSON and focused editors for settings, sniffing, and streamSettings.",
@@ -2074,4 +2080,4 @@
     "statusDown": "DOWN",
     "statusUp": "UP"
   }
-}
+}

+ 9 - 3
internal/web/translation/es-ES.json

@@ -407,10 +407,16 @@
       "sniffingDomainsExcluded": "Dominios excluidos",
       "decryption": "Descifrado",
       "encryption": "Cifrado",
-      "vlessAuthX25519": "Autenticación X25519",
-      "vlessAuthMlkem768": "Autenticación ML-KEM-768",
+      "vlessAuthX25519": "X25519 (native)",
+      "vlessAuthMlkem768": "ML-KEM-768 (native)",
+      "vlessAuthX25519Xorpub": "X25519 (xorpub)",
+      "vlessAuthX25519Random": "X25519 (random)",
+      "vlessAuthMlkem768Xorpub": "ML-KEM-768 (xorpub)",
+      "vlessAuthMlkem768Random": "ML-KEM-768 (random)",
       "vlessAuthCustom": "Personalizado",
       "vlessAuthSelected": "Seleccionado: {auth}",
+      "vlessAuthGenerate": "Generar claves",
+      "vlessAuthGenerateButton": "Generar",
       "advanced": {
         "title": "Secciones JSON del inbound",
         "subtitle": "JSON completo del inbound y editores específicos para settings, sniffing y streamSettings.",
@@ -2071,4 +2077,4 @@
     "statusDown": "CAÍDO",
     "statusUp": "ACTIVO"
   }
-}
+}

+ 9 - 3
internal/web/translation/fa-IR.json

@@ -407,10 +407,16 @@
       "sniffingDomainsExcluded": "دامنه‌های مستثنا",
       "decryption": "رمزگشایی",
       "encryption": "رمزنگاری",
-      "vlessAuthX25519": "احراز X25519",
-      "vlessAuthMlkem768": "احراز ML-KEM-768",
+      "vlessAuthX25519": "X25519 (native)",
+      "vlessAuthMlkem768": "ML-KEM-768 (native)",
+      "vlessAuthX25519Xorpub": "X25519 (xorpub)",
+      "vlessAuthX25519Random": "X25519 (random)",
+      "vlessAuthMlkem768Xorpub": "ML-KEM-768 (xorpub)",
+      "vlessAuthMlkem768Random": "ML-KEM-768 (random)",
       "vlessAuthCustom": "سفارشی",
       "vlessAuthSelected": "انتخاب‌شده: {auth}",
+      "vlessAuthGenerate": "تولید کلیدها",
+      "vlessAuthGenerateButton": "تولید",
       "advanced": {
         "title": "بخش‌های JSON اینباند",
         "subtitle": "JSON کامل اینباند و ویرایشگرهای جداگانه برای settings، sniffing و streamSettings.",
@@ -2071,4 +2077,4 @@
     "statusDown": "قطع",
     "statusUp": "وصل"
   }
-}
+}

+ 9 - 3
internal/web/translation/id-ID.json

@@ -407,10 +407,16 @@
       "sniffingDomainsExcluded": "Domain yang dikecualikan",
       "decryption": "Dekripsi",
       "encryption": "Enkripsi",
-      "vlessAuthX25519": "Auth X25519",
-      "vlessAuthMlkem768": "Auth ML-KEM-768",
+      "vlessAuthX25519": "X25519 (native)",
+      "vlessAuthMlkem768": "ML-KEM-768 (native)",
+      "vlessAuthX25519Xorpub": "X25519 (xorpub)",
+      "vlessAuthX25519Random": "X25519 (random)",
+      "vlessAuthMlkem768Xorpub": "ML-KEM-768 (xorpub)",
+      "vlessAuthMlkem768Random": "ML-KEM-768 (random)",
       "vlessAuthCustom": "Khusus",
       "vlessAuthSelected": "Dipilih: {auth}",
+      "vlessAuthGenerate": "Buat kunci",
+      "vlessAuthGenerateButton": "Buat",
       "advanced": {
         "title": "Bagian JSON inbound",
         "subtitle": "JSON inbound lengkap dan editor fokus untuk settings, sniffing, dan streamSettings.",
@@ -2071,4 +2077,4 @@
     "statusDown": "MATI",
     "statusUp": "AKTIF"
   }
-}
+}

+ 9 - 3
internal/web/translation/ja-JP.json

@@ -407,10 +407,16 @@
       "sniffingDomainsExcluded": "除外するドメイン",
       "decryption": "復号",
       "encryption": "暗号化",
-      "vlessAuthX25519": "X25519 認証",
-      "vlessAuthMlkem768": "ML-KEM-768 認証",
+      "vlessAuthX25519": "X25519 (native)",
+      "vlessAuthMlkem768": "ML-KEM-768 (native)",
+      "vlessAuthX25519Xorpub": "X25519 (xorpub)",
+      "vlessAuthX25519Random": "X25519 (random)",
+      "vlessAuthMlkem768Xorpub": "ML-KEM-768 (xorpub)",
+      "vlessAuthMlkem768Random": "ML-KEM-768 (random)",
       "vlessAuthCustom": "カスタム",
       "vlessAuthSelected": "選択中: {auth}",
+      "vlessAuthGenerate": "鍵を生成",
+      "vlessAuthGenerateButton": "生成",
       "advanced": {
         "title": "インバウンド JSON セクション",
         "subtitle": "インバウンド全体の JSON と、settings、sniffing、streamSettings 用の専用エディター。",
@@ -2071,4 +2077,4 @@
     "statusDown": "ダウン",
     "statusUp": "アップ"
   }
-}
+}

+ 9 - 3
internal/web/translation/pt-BR.json

@@ -407,10 +407,16 @@
       "sniffingDomainsExcluded": "Domínios excluídos",
       "decryption": "Descriptografia",
       "encryption": "Criptografia",
-      "vlessAuthX25519": "Autenticação X25519",
-      "vlessAuthMlkem768": "Autenticação ML-KEM-768",
+      "vlessAuthX25519": "X25519 (native)",
+      "vlessAuthMlkem768": "ML-KEM-768 (native)",
+      "vlessAuthX25519Xorpub": "X25519 (xorpub)",
+      "vlessAuthX25519Random": "X25519 (random)",
+      "vlessAuthMlkem768Xorpub": "ML-KEM-768 (xorpub)",
+      "vlessAuthMlkem768Random": "ML-KEM-768 (random)",
       "vlessAuthCustom": "Personalizado",
       "vlessAuthSelected": "Selecionado: {auth}",
+      "vlessAuthGenerate": "Gerar chaves",
+      "vlessAuthGenerateButton": "Gerar",
       "advanced": {
         "title": "Seções JSON do inbound",
         "subtitle": "JSON completo do inbound e editores específicos para settings, sniffing e streamSettings.",
@@ -2071,4 +2077,4 @@
     "statusDown": "INATIVO",
     "statusUp": "ATIVO"
   }
-}
+}

+ 9 - 3
internal/web/translation/ru-RU.json

@@ -407,10 +407,16 @@
       "sniffingDomainsExcluded": "Исключённые домены",
       "decryption": "Расшифрование",
       "encryption": "Шифрование",
-      "vlessAuthX25519": "Аутентификация X25519",
-      "vlessAuthMlkem768": "Аутентификация ML-KEM-768",
+      "vlessAuthX25519": "X25519 (native)",
+      "vlessAuthMlkem768": "ML-KEM-768 (native)",
+      "vlessAuthX25519Xorpub": "X25519 (xorpub)",
+      "vlessAuthX25519Random": "X25519 (random)",
+      "vlessAuthMlkem768Xorpub": "ML-KEM-768 (xorpub)",
+      "vlessAuthMlkem768Random": "ML-KEM-768 (random)",
       "vlessAuthCustom": "Свой",
       "vlessAuthSelected": "Выбрано: {auth}",
+      "vlessAuthGenerate": "Генерация ключей",
+      "vlessAuthGenerateButton": "Сгенерировать",
       "advanced": {
         "title": "Разделы JSON входящего",
         "subtitle": "Полный JSON входящего и отдельные редакторы для settings, sniffing и streamSettings.",
@@ -2071,4 +2077,4 @@
     "statusDown": "НЕДОСТУПЕН",
     "statusUp": "РАБОТАЕТ"
   }
-}
+}

+ 9 - 3
internal/web/translation/tr-TR.json

@@ -407,10 +407,16 @@
       "sniffingDomainsExcluded": "Hariç tutulan alan adları",
       "decryption": "Şifre Çözme",
       "encryption": "Şifreleme",
-      "vlessAuthX25519": "X25519 Kimlik Doğrulama",
-      "vlessAuthMlkem768": "ML-KEM-768 Kimlik Doğrulama",
+      "vlessAuthX25519": "X25519 (native)",
+      "vlessAuthMlkem768": "ML-KEM-768 (native)",
+      "vlessAuthX25519Xorpub": "X25519 (xorpub)",
+      "vlessAuthX25519Random": "X25519 (random)",
+      "vlessAuthMlkem768Xorpub": "ML-KEM-768 (xorpub)",
+      "vlessAuthMlkem768Random": "ML-KEM-768 (random)",
       "vlessAuthCustom": "Özel",
       "vlessAuthSelected": "Seçili: {auth}",
+      "vlessAuthGenerate": "Anahtar oluştur",
+      "vlessAuthGenerateButton": "Oluştur",
       "advanced": {
         "title": "Gelen Bağlantı JSON Bölümleri",
         "subtitle": "Tam gelen bağlantı JSON'u ve settings, sniffing, streamSettings için odaklanmış düzenleyiciler.",
@@ -2071,4 +2077,4 @@
     "statusDown": "ÇEVRİMDIŞI",
     "statusUp": "ÇEVRİMİÇİ"
   }
-}
+}

+ 9 - 3
internal/web/translation/uk-UA.json

@@ -407,10 +407,16 @@
       "sniffingDomainsExcluded": "Виключені домени",
       "decryption": "Розшифрування",
       "encryption": "Шифрування",
-      "vlessAuthX25519": "Автентифікація X25519",
-      "vlessAuthMlkem768": "Автентифікація ML-KEM-768",
+      "vlessAuthX25519": "X25519 (native)",
+      "vlessAuthMlkem768": "ML-KEM-768 (native)",
+      "vlessAuthX25519Xorpub": "X25519 (xorpub)",
+      "vlessAuthX25519Random": "X25519 (random)",
+      "vlessAuthMlkem768Xorpub": "ML-KEM-768 (xorpub)",
+      "vlessAuthMlkem768Random": "ML-KEM-768 (random)",
       "vlessAuthCustom": "Користувацький",
       "vlessAuthSelected": "Вибрано: {auth}",
+      "vlessAuthGenerate": "Генерація ключів",
+      "vlessAuthGenerateButton": "Згенерувати",
       "advanced": {
         "title": "Розділи JSON вхідного",
         "subtitle": "Повний JSON вхідного та окремі редактори для settings, sniffing і streamSettings.",
@@ -2071,4 +2077,4 @@
     "statusDown": "НЕДОСТУПНО",
     "statusUp": "ДОСТУПНО"
   }
-}
+}

+ 9 - 3
internal/web/translation/vi-VN.json

@@ -407,10 +407,16 @@
       "sniffingDomainsExcluded": "Tên miền bị loại trừ",
       "decryption": "Giải mã",
       "encryption": "Mã hóa",
-      "vlessAuthX25519": "Xác thực X25519",
-      "vlessAuthMlkem768": "Xác thực ML-KEM-768",
+      "vlessAuthX25519": "X25519 (native)",
+      "vlessAuthMlkem768": "ML-KEM-768 (native)",
+      "vlessAuthX25519Xorpub": "X25519 (xorpub)",
+      "vlessAuthX25519Random": "X25519 (random)",
+      "vlessAuthMlkem768Xorpub": "ML-KEM-768 (xorpub)",
+      "vlessAuthMlkem768Random": "ML-KEM-768 (random)",
       "vlessAuthCustom": "Tùy chỉnh",
       "vlessAuthSelected": "Đã chọn: {auth}",
+      "vlessAuthGenerate": "Tạo khóa",
+      "vlessAuthGenerateButton": "Tạo",
       "advanced": {
         "title": "Các phần JSON của inbound",
         "subtitle": "JSON inbound đầy đủ và các trình chỉnh sửa riêng cho settings, sniffing và streamSettings.",
@@ -2071,4 +2077,4 @@
     "statusDown": "NGỪNG HOẠT ĐỘNG",
     "statusUp": "HOẠT ĐỘNG"
   }
-}
+}

+ 9 - 3
internal/web/translation/zh-CN.json

@@ -407,10 +407,16 @@
       "sniffingDomainsExcluded": "排除的域名",
       "decryption": "解密",
       "encryption": "加密",
-      "vlessAuthX25519": "X25519 认证",
-      "vlessAuthMlkem768": "ML-KEM-768 认证",
+      "vlessAuthX25519": "X25519 (native)",
+      "vlessAuthMlkem768": "ML-KEM-768 (native)",
+      "vlessAuthX25519Xorpub": "X25519 (xorpub)",
+      "vlessAuthX25519Random": "X25519 (random)",
+      "vlessAuthMlkem768Xorpub": "ML-KEM-768 (xorpub)",
+      "vlessAuthMlkem768Random": "ML-KEM-768 (random)",
       "vlessAuthCustom": "自定义",
       "vlessAuthSelected": "已选择:{auth}",
+      "vlessAuthGenerate": "生成密钥",
+      "vlessAuthGenerateButton": "生成",
       "advanced": {
         "title": "入站 JSON 部分",
         "subtitle": "完整入站 JSON 以及针对 settings、sniffing 和 streamSettings 的专用编辑器。",
@@ -2071,4 +2077,4 @@
     "statusDown": "断开",
     "statusUp": "恢复"
   }
-}
+}

+ 9 - 3
internal/web/translation/zh-TW.json

@@ -407,10 +407,16 @@
       "sniffingDomainsExcluded": "排除的網域",
       "decryption": "解密",
       "encryption": "加密",
-      "vlessAuthX25519": "X25519 認證",
-      "vlessAuthMlkem768": "ML-KEM-768 認證",
+      "vlessAuthX25519": "X25519 (native)",
+      "vlessAuthMlkem768": "ML-KEM-768 (native)",
+      "vlessAuthX25519Xorpub": "X25519 (xorpub)",
+      "vlessAuthX25519Random": "X25519 (random)",
+      "vlessAuthMlkem768Xorpub": "ML-KEM-768 (xorpub)",
+      "vlessAuthMlkem768Random": "ML-KEM-768 (random)",
       "vlessAuthCustom": "自訂",
       "vlessAuthSelected": "已選擇:{auth}",
+      "vlessAuthGenerate": "生成密鑰",
+      "vlessAuthGenerateButton": "生成",
       "advanced": {
         "title": "入站 JSON 部分",
         "subtitle": "完整入站 JSON 以及針對 settings、sniffing 和 streamSettings 的專用編輯器。",
@@ -2071,4 +2077,4 @@
     "statusDown": "中斷",
     "statusUp": "恢復"
   }
-}
+}