mirror of
https://github.com/zap-proto/spec.git
synced 2026-10-30 15:04:03 +00:00
Replace pqcrypto-dilithium 0.5 (PQClean Round 3 Dilithium3) with pqcrypto-mldsa 0.1.2 (FIPS 204 final ML-DSA-65). Use the ctx-aware sign/verify (detached_sign_ctx / verify_detached_signature_ctx) with ZWING_DOMAIN = "lux.zwing.v1" so signatures verify under cloudflare/ circl mldsa65 (Go), dilithium-py (Python), and @noble/post-quantum (TS) byte-for-byte. Adds tests/zwing_interop_go.rs which spawns the Go test server binary at /tmp/zwing-test-server and runs a full Rust client → Go server handshake + AEAD echo round trip over real TCP. Test passes. The 23 in-Rust tests still pass under the new ML-DSA-65 impl. Z-Wing full-handshake interop is now PROVEN across all four implementations: Go ↔ Python (py/test_interop_go.py) Go ↔ TypeScript (js/interop_go.test.ts) Go ↔ Rust (zap-protocol/zap tests/zwing_interop_go.rs) The X-Wing combiner KAT 72df2088314a73de80c21d9593f13fcd5629c800c70b1507f0dd918fde5fe4ed continues to pin in lockstep across all four languages.
74 lines
2.4 KiB
Rust
74 lines
2.4 KiB
Rust
//! End-to-end interop test: Rust Z-Wing client ↔ Go Z-Wing server.
|
|
//!
|
|
//! Spawns the Go test server binary at `/tmp/zwing-test-server` (built
|
|
//! from `~/work/lux/zwing/cmd/zwing-test-server`), reads its listen
|
|
//! address and identity hex from stdout, opens a TCP socket, and runs
|
|
//! a full handshake + AEAD echo round trip.
|
|
//!
|
|
//! The test is skipped if the binary isn't present so it stays
|
|
//! cargo-clean on machines without the Go side built.
|
|
|
|
#![cfg(feature = "zwing")]
|
|
|
|
use std::io::{BufRead, BufReader, Read, Write};
|
|
use std::net::TcpStream;
|
|
use std::path::Path;
|
|
use std::process::{Command, Stdio};
|
|
|
|
use zap::zwing::{run_initiator, Channel, Identity, IdentityPublic};
|
|
|
|
const SERVER_BIN: &str = "/tmp/zwing-test-server";
|
|
|
|
#[test]
|
|
fn rust_client_to_go_server_handshake_and_echo() {
|
|
if !Path::new(SERVER_BIN).exists() {
|
|
eprintln!("skipping: {SERVER_BIN} missing — build the Go side first");
|
|
return;
|
|
}
|
|
|
|
let mut server = Command::new(SERVER_BIN)
|
|
.arg("-addr")
|
|
.arg("127.0.0.1:0")
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.spawn()
|
|
.expect("spawn go server");
|
|
|
|
let stdout = server.stdout.take().expect("stdout");
|
|
let mut reader = BufReader::new(stdout);
|
|
let mut addr = String::new();
|
|
reader.read_line(&mut addr).unwrap();
|
|
let mut pub_hex = String::new();
|
|
reader.read_line(&mut pub_hex).unwrap();
|
|
let addr = addr.trim().to_string();
|
|
let pub_hex = pub_hex.trim().to_string();
|
|
assert!(!addr.is_empty());
|
|
assert!(!pub_hex.is_empty());
|
|
|
|
let pub_bytes = hex::decode(&pub_hex).expect("hex");
|
|
let server_pub = IdentityPublic::from_bytes(&pub_bytes).expect("identity parse");
|
|
|
|
let client = Identity::generate();
|
|
let mut sock = TcpStream::connect(&addr).expect("connect");
|
|
sock.set_read_timeout(Some(std::time::Duration::from_secs(10)))
|
|
.ok();
|
|
|
|
let out = run_initiator(&mut sock, &client, Some(&server_pub)).expect("handshake");
|
|
let mut chan = Channel::new(sock, out, true);
|
|
|
|
let payload = b"hello from Rust initiator";
|
|
chan.send(payload).expect("send");
|
|
let mut buf = Vec::new();
|
|
chan.recv(&mut buf).expect("recv");
|
|
assert_eq!(buf, payload);
|
|
|
|
let status = server.wait_with_output().expect("wait");
|
|
if !status.status.success() {
|
|
panic!(
|
|
"go server exit={:?}\nstderr:\n{}",
|
|
status.status,
|
|
String::from_utf8_lossy(&status.stderr)
|
|
);
|
|
}
|
|
}
|