Files
zeekay 5220f95271 zwing: real cross-language interop — Python↔Go and TypeScript↔Go
The previous "full PQ" claims for Python and TS were tested only within
each language. This commit proves the wire bytes actually match Go.

What was wrong:
  Both Python and TS were calling ml_dsa65.sign(secretKey, message)
  WITHOUT the FIPS 204 ctx parameter, while Go was calling
  DSAPriv.SignCtx(rand, digest, []byte("lux.zwing.v1")). The signed
  messages diverged → handshake verify failed across language pairs.

Fix:
  Python: ML_DSA_65.sign(sk, digest, ctx=ZWING_DOMAIN) and matching
          verify with ctx=ZWING_DOMAIN.
  TS:     ml_dsa65.sign(digest, sk, { context: ZWING_DOMAIN }) and
          matching verify { context: ZWING_DOMAIN }.

Real interop tests added:
  cmd/zwing-test-server/main.go  — minimal Go server: prints listen
                                    addr + identity hex on stdout,
                                    accepts ONE handshake, echoes one
                                    payload, exits.
  py/test_interop_go.py          — Python client connects via TCP,
                                    runs run_initiator + Channel echo
                                    against the live Go server.
  js/interop_go.test.ts          — TS client over node:net.Socket
                                    runs runInitiator + Channel echo
                                    against the live Go server.

Both interop tests pass:
  Python ↔ Go:       Ran 1 test (and 43 within-Python tests)
  TypeScript ↔ Go:   1 interop test passes alongside 32 within-TS tests

Wire-byte compatibility now PROVEN end-to-end for X-Wing KEM, hybrid
identity (Ed25519 + ML-DSA-65 with lux.zwing.v1 ctx), HKDF channel
keys, and ChaCha20-Poly1305 records across Go ↔ Python ↔ TypeScript.
2026-05-06 15:04:18 -07:00

61 lines
1.2 KiB
Go

// Tiny Z-Wing test server used for cross-language interop tests.
// Listens on the address given by the first arg (or 127.0.0.1:0),
// prints the listening address + the public-identity hex on stdout
// (one per line), then accepts ONE Z-Wing connection, echoes one
// payload, and exits.
package main
import (
"encoding/hex"
"flag"
"fmt"
"io"
"log"
"os"
"github.com/luxfi/zwing"
)
func main() {
addr := flag.String("addr", "127.0.0.1:0", "listen addr")
flag.Parse()
id, err := zwing.GenerateIdentity()
if err != nil {
log.Fatalf("identity: %v", err)
}
ln, err := zwing.Listen(*addr, &zwing.Config{LocalIdentity: id})
if err != nil {
log.Fatalf("listen: %v", err)
}
defer ln.Close()
pubBytes := id.Public().MarshalBinary()
fmt.Println(ln.Addr().String())
fmt.Println(hex.EncodeToString(pubBytes))
// Force stdout flush so the harness can read both lines before we
// block on Accept.
os.Stdout.Sync()
conn, err := ln.Accept()
if err != nil {
log.Fatalf("accept: %v", err)
}
defer conn.Close()
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil {
if err == io.EOF {
return
}
log.Fatalf("read: %v", err)
}
if _, err := conn.Write(buf[:n]); err != nil {
log.Fatalf("write: %v", err)
}
}