Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f61c14e
VLESS protocol: Add lightweight Post-Quantum ML-KEM-768-based PFS 1-R…
RPRX Aug 10, 2025
fc137d2
Fix test
Fangliding Aug 10, 2025
3e19bf9
Rename reuse/index -> ticket
RPRX Aug 11, 2025
2e6a883
Revert "Fix test"
RPRX Aug 11, 2025
7ffb555
Add optional aes128xor layer
RPRX Aug 11, 2025
ec1cc35
Mainly reverse VLESS Client Hello
RPRX Aug 12, 2025
5c61142
Generate new key when nonce reaches max value
RPRX Aug 12, 2025
23d7aad
Add Close() for ServerInstance; Fix server's nonce overflow
RPRX Aug 12, 2025
3c20bdd
Add 5-bytes header for client/server/ticket hello and server random
RPRX Aug 13, 2025
1720be1
aes128xor (all) -> xored (optimized)
RPRX Aug 13, 2025
0fd7691
Fix reading ticket hello
RPRX Aug 13, 2025
09cc92c
chore
RPRX Aug 13, 2025
7f778a4
SHA256(nfsEKeyBytes) for XOR's key
RPRX Aug 13, 2025
2807ee4
Allow paddings before handshake; CTR 128->256; Fix panic
RPRX Aug 14, 2025
bfe4820
Fix 1/67000000 chance's server panic; Refine comments
RPRX Aug 14, 2025
d1fb485
Add hash11(nfsEKeyBytes) to client/ticket hello; Support XTLS Vision …
RPRX Aug 17, 2025
4958070
Use SHA3-256 instead of SHA2-256; Support XTLS Vision for random appe…
RPRX Aug 18, 2025
84835be
Support VLESS Encryption (native/random) + XTLS Vision + Any Transpor…
RPRX Aug 19, 2025
373558e
Use X25519 for XOR; Add "divide" (ECH, before and includes type 0); C…
RPRX Aug 20, 2025
38cc306
Use nfsAEAD & pfsAEAD for paddings; Refine comments
RPRX Aug 21, 2025
b33555c
Empty hash11 for "native"; Verify NFS paddings; Fix 1-RTT's `peerRandom`
RPRX Aug 22, 2025
ad71406
REFACTOR
RPRX Aug 24, 2025
0199dea
Ready to release
RPRX Aug 24, 2025
fce1195
Final changes
RPRX Aug 25, 2025
b0b2209
Changes before merging
RPRX Aug 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions common/protocol/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,20 +79,18 @@ type CommandSwitchAccount struct {
}

var (
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
// Keep in sync with crypto/tls/cipher_suites.go.
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
// Keep in sync with crypto/aes/cipher_s390x.go.
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR &&
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH
hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le"

hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
HasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64
)

func (sc *SecurityConfig) GetSecurityType() SecurityType {
if sc == nil || sc.Type == SecurityType_AUTO {
if hasAESGCMHardwareSupport {
if HasAESGCMHardwareSupport {
return SecurityType_AES128_GCM
}
return SecurityType_CHACHA20_POLY1305
Expand Down
85 changes: 76 additions & 9 deletions infra/conf/vless.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package conf

import (
"encoding/base64"
"encoding/json"
"path/filepath"
"runtime"
Expand Down Expand Up @@ -68,10 +69,45 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
config.Clients[idx] = user
}

if c.Decryption != "none" {
return nil, errors.New(`VLESS settings: please add/set "decryption":"none" to every settings`)
}
config.Decryption = c.Decryption
if !func() bool {
s := strings.Split(config.Decryption, ".")
if len(s) < 4 || s[0] != "mlkem768x25519plus" {
return false
}
switch s[1] {
case "native":
case "xorpub":
config.XorMode = 1
case "random":
config.XorMode = 2
default:
return false
}
if s[2] != "1rtt" {
t := strings.TrimSuffix(s[2], "s")
if t == s[2] {
return false
}
i, err := strconv.Atoi(t)
if err != nil {
return false
}
config.Seconds = uint32(i)
}
for i := 3; i < len(s); i++ {
if b, _ := base64.RawURLEncoding.DecodeString(s[i]); len(b) != 32 && len(b) != 64 {
return false
}
}
config.Decryption = config.Decryption[27+len(s[2]):]
return true
}() && config.Decryption != "none" {
if config.Decryption == "" {
return nil, errors.New(`VLESS settings: please add/set "decryption":"none" to every settings`)
}
return nil, errors.New(`VLESS settings: unsupported "decryption": ` + config.Decryption)
}

for _, fb := range c.Fallbacks {
var i uint16
Expand Down Expand Up @@ -143,16 +179,16 @@ type VLessOutboundConfig struct {
func (c *VLessOutboundConfig) Build() (proto.Message, error) {
config := new(outbound.Config)

if len(c.Vnext) == 0 {
return nil, errors.New(`VLESS settings: "vnext" is empty`)
if len(c.Vnext) != 1 {
return nil, errors.New(`VLESS settings: "vnext" should have one and only one member`)
}
config.Vnext = make([]*protocol.ServerEndpoint, len(c.Vnext))
for idx, rec := range c.Vnext {
if rec.Address == nil {
return nil, errors.New(`VLESS vnext: "address" is not set`)
}
if len(rec.Users) == 0 {
return nil, errors.New(`VLESS vnext: "users" is empty`)
if len(rec.Users) != 1 {
return nil, errors.New(`VLESS vnext: "users" should have one and only one member`)
}
spec := &protocol.ServerEndpoint{
Address: rec.Address.Build(),
Expand Down Expand Up @@ -181,8 +217,39 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
return nil, errors.New(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`)
}

if account.Encryption != "none" {
return nil, errors.New(`VLESS users: please add/set "encryption":"none" for every user`)
if !func() bool {
s := strings.Split(account.Encryption, ".")
if len(s) < 4 || s[0] != "mlkem768x25519plus" {
return false
}
switch s[1] {
case "native":
case "xorpub":
account.XorMode = 1
case "random":
account.XorMode = 2
default:
return false
}
switch s[2] {
case "1rtt":
case "0rtt":
account.Seconds = 1
default:
return false
}
for i := 3; i < len(s); i++ {
if b, _ := base64.RawURLEncoding.DecodeString(s[i]); len(b) != 32 && len(b) != 1184 {
return false
}
}
account.Encryption = account.Encryption[27+len(s[2]):]
return true
}() && account.Encryption != "none" {
if account.Encryption == "" {
return nil, errors.New(`VLESS users: please add/set "encryption":"none" for every user`)
}
return nil, errors.New(`VLESS users: unsupported "encryption": ` + account.Encryption)
}

user.Account = serial.ToTypedMessage(account)
Expand Down
1 change: 1 addition & 0 deletions main/commands/all/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ func init() {
cmdX25519,
cmdWG,
cmdMLDSA65,
cmdMLKEM768,
)
}
47 changes: 20 additions & 27 deletions main/commands/all/curve25519.go
Original file line number Diff line number Diff line change
@@ -1,58 +1,51 @@
package all

import (
"crypto/ecdh"
"crypto/rand"
"encoding/base64"
"fmt"

"golang.org/x/crypto/curve25519"
"lukechampine.com/blake3"
)

func Curve25519Genkey(StdEncoding bool, input_base64 string) {
var output string
var err error
var privateKey, publicKey []byte
var encoding *base64.Encoding
if *input_stdEncoding || StdEncoding {
encoding = base64.StdEncoding
} else {
encoding = base64.RawURLEncoding
}

var privateKey []byte
if len(input_base64) > 0 {
privateKey, err = encoding.DecodeString(input_base64)
if err != nil {
output = err.Error()
goto out
}
if len(privateKey) != curve25519.ScalarSize {
output = "Invalid length of private key."
goto out
privateKey, _ = encoding.DecodeString(input_base64)
if len(privateKey) != 32 {
fmt.Println("Invalid length of X25519 private key.")
return
}
}

if privateKey == nil {
privateKey = make([]byte, curve25519.ScalarSize)
if _, err = rand.Read(privateKey); err != nil {
output = err.Error()
goto out
}
privateKey = make([]byte, 32)
rand.Read(privateKey)
}

// Modify random bytes using algorithm described at:
// https://cr.yp.to/ecdh.html.
// https://cr.yp.to/ecdh.html
// (Just to make sure printing the real private key)
privateKey[0] &= 248
privateKey[31] &= 127
privateKey[31] |= 64

if publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint); err != nil {
output = err.Error()
goto out
key, err := ecdh.X25519().NewPrivateKey(privateKey)
if err != nil {
fmt.Println(err.Error())
return
}

output = fmt.Sprintf("Private key: %v\nPublic key: %v",
password := key.PublicKey().Bytes()
hash32 := blake3.Sum256(password)
fmt.Printf("PrivateKey: %v\nPassword: %v\nHash32: %v",
encoding.EncodeToString(privateKey),
encoding.EncodeToString(publicKey))
out:
fmt.Println(output)
encoding.EncodeToString(password),
encoding.EncodeToString(hash32[:]))
}
14 changes: 9 additions & 5 deletions main/commands/all/mldsa65.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import (

var cmdMLDSA65 = &base.Command{
UsageLine: `{{.Exec}} mldsa65 [-i "seed (base64.RawURLEncoding)"]`,
Short: `Generate key pair for ML-DSA-65 post-quantum signature`,
Short: `Generate key pair for ML-DSA-65 post-quantum signature (REALITY)`,
Long: `
Generate key pair for ML-DSA-65 post-quantum signature.
Generate key pair for ML-DSA-65 post-quantum signature (REALITY).

Random: {{.Exec}} mldsa65

Expand All @@ -25,12 +25,16 @@ func init() {
cmdMLDSA65.Run = executeMLDSA65 // break init loop
}

var input_seed = cmdMLDSA65.Flag.String("i", "", "")
var input_mldsa65 = cmdMLDSA65.Flag.String("i", "", "")

func executeMLDSA65(cmd *base.Command, args []string) {
var seed [32]byte
if len(*input_seed) > 0 {
s, _ := base64.RawURLEncoding.DecodeString(*input_seed)
if len(*input_mldsa65) > 0 {
s, _ := base64.RawURLEncoding.DecodeString(*input_mldsa65)
if len(s) != 32 {
fmt.Println("Invalid length of ML-DSA-65 seed.")
return
}
seed = [32]byte(s)
} else {
rand.Read(seed[:])
Expand Down
50 changes: 50 additions & 0 deletions main/commands/all/mlkem768.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package all

import (
"crypto/mlkem"
"crypto/rand"
"encoding/base64"
"fmt"

"github.com/xtls/xray-core/main/commands/base"
"lukechampine.com/blake3"
)

var cmdMLKEM768 = &base.Command{
UsageLine: `{{.Exec}} mlkem768 [-i "seed (base64.RawURLEncoding)"]`,
Short: `Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS)`,
Long: `
Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS).

Random: {{.Exec}} mlkem768

From seed: {{.Exec}} mlkem768 -i "seed (base64.RawURLEncoding)"
`,
}

func init() {
cmdMLKEM768.Run = executeMLKEM768 // break init loop
}

var input_mlkem768 = cmdMLKEM768.Flag.String("i", "", "")

func executeMLKEM768(cmd *base.Command, args []string) {
var seed [64]byte
if len(*input_mlkem768) > 0 {
s, _ := base64.RawURLEncoding.DecodeString(*input_mlkem768)
if len(s) != 64 {
fmt.Println("Invalid length of ML-KEM-768 seed.")
return
}
seed = [64]byte(s)
} else {
rand.Read(seed[:])
}
key, _ := mlkem.NewDecapsulationKey768(seed[:])
client := key.EncapsulationKey().Bytes()
hash32 := blake3.Sum256(client)
fmt.Printf("Seed: %v\nClient: %v\nHash32: %v",
base64.RawURLEncoding.EncodeToString(seed[:]),
base64.RawURLEncoding.EncodeToString(client),
base64.RawURLEncoding.EncodeToString(hash32[:]))
}
4 changes: 2 additions & 2 deletions main/commands/all/uuid.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (

var cmdUUID = &base.Command{
UsageLine: `{{.Exec}} uuid [-i "example"]`,
Short: `Generate UUIDv4 or UUIDv5`,
Short: `Generate UUIDv4 or UUIDv5 (VLESS)`,
Long: `
Generate UUIDv4 or UUIDv5.
Generate UUIDv4 or UUIDv5 (VLESS).

UUIDv4 (random): {{.Exec}} uuid

Expand Down
4 changes: 2 additions & 2 deletions main/commands/all/wg.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (

var cmdWG = &base.Command{
UsageLine: `{{.Exec}} wg [-i "private key (base64.StdEncoding)"]`,
Short: `Generate key pair for wireguard key exchange`,
Short: `Generate key pair for X25519 key exchange (WireGuard)`,
Long: `
Generate key pair for wireguard key exchange.
Generate key pair for X25519 key exchange (WireGuard).

Random: {{.Exec}} wg

Expand Down
4 changes: 2 additions & 2 deletions main/commands/all/x25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (

var cmdX25519 = &base.Command{
UsageLine: `{{.Exec}} x25519 [-i "private key (base64.RawURLEncoding)"] [--std-encoding]`,
Short: `Generate key pair for x25519 key exchange`,
Short: `Generate key pair for X25519 key exchange (VLESS, REALITY)`,
Long: `
Generate key pair for x25519 key exchange.
Generate key pair for X25519 key exchange (VLESS, REALITY).

Random: {{.Exec}} x25519

Expand Down
Loading
Loading