Signing & verification
sign: is an outbound field on a Phaze Transport action: the worker signs the response body, the browser verifies it before reading, and a tampered body throws. The verify is injected into the compiled call — the consumer imports only actions, hand-rolls no crypto, and (for native algorithms) ships zero crypto bytes.
getSession: newAction({ flatbuffer: SessionManifest, sign: 'ed25519', // ← sign the response handler: async ({ id }, { env }) => env.SESSIONS.get(id),})What’s on the wire
Section titled “What’s on the wire”The signature is detached, in an X-Phaze-Sig header — the body stays a clean buffer (composes with flatbuffer:, zero-copy):
HTTP/2 200content-type: application/x-flatbufx-phaze-sig: ed25519:<base64 signature><raw FlatBuffer bytes>The worker signs the exact bytes the client receives (the FlatBuffer buffer, or the serialized JSON). The client reads X-Phaze-Sig, verifies it against the response bytes, and only then proceeds to the inline reads / JSON parse. On failure it throws → the async signal’s .error.
Key management
Section titled “Key management”Signatures use one raw-hex key pair, the same format on both runtimes (the TS worker wraps the raw Ed25519 seed in the fixed PKCS8 prefix for Web Crypto; Rust’s phaze-crypto reads the same hex via from_hex):
- Private —
PHAZE_SIGN_KEY, a worker secret (wrangler secret put). Signs. - Public —
PUBLIC_PHAZE_SIGN_KEY, inlined into the client at build viaphaze:env/client. Verifies.
The compiled verify embeds the 32-byte public key; rotation is a redeploy, no extra fetch.
Compile-time dispatch → per-algorithm emission
Section titled “Compile-time dispatch → per-algorithm emission”The registry tells the compiler the algorithm for each action at build time. Given that, it picks the emission:
| Algo (from the field) | Emission | Runtime cost |
|---|---|---|
ed25519, es256, aes-gcm (Web Crypto native) | inline native verify/decrypt | subtle.verify(...) inline, no module, no indirection |
PQC ml-dsa / ml-kem (WASM) | gated module pulled for that action only | ships only when the browser must do PQC |
hybrid ed25519+ml-dsa | native inline and WASM gated, both required | native part stays inline |
The classical rows (ed25519 / es256 / aes-gcm) ship today. The PQC sign: wiring ships on the Rust runtime: sign = "ed25519+ml-dsa" emits the dual X-Phaze-Sig and ml_dsa::verify_response_sig checks the ml-dsa half server-side (phaze::crypto::ml_dsa), runtime-proven by a live e2e (see Post-quantum & at-rest). The browser still verifies only the ed25519 half — 0 WASM.
So native algos compile to exactly the inline subtle.verify they’d be by hand. Zero runtime difference. The registry is purely a build-time decision — there’s no runtime algo-detection overhead (which would be the slow, all-verifiers-present way).
Detection is compile-time, not runtime
Section titled “Detection is compile-time, not runtime”The field (sign: 'ed25519') is the signal; the compiler reads it and emits the native-inline path. The runtime X-Phaze-Sig: <algo>:<sig> header still carries the algorithm tag + signature (and, for rotation, a key id) so the emitted verifier checks the right bytes — but which verifier ships is decided at build. No runtime branching to “find the right algo,” no shipping every verifier just in case.
Hybrid signatures
Section titled “Hybrid signatures”sign: 'ed25519+ml-dsa' requests a composite signature — two independent signatures over the body, both must verify (classical and post-quantum, defense-in-depth: a break in one doesn’t forge). The browser verifies the classical half natively for its live check; the post-quantum half is the long-lived assurance (see Post-quantum & at-rest).
Inbound: verify:
Section titled “Inbound: verify:”The mirror of sign: is inbound verification — checking a signed request (a webhook from an untrusted sender). On the Rust runtime that’s the verify= attribute (Ed25519 / JWT); the TS verify: field is planned. Note the direction: sign: signs responses (the browser verifies); verify: checks requests (the server verifies). Opposite ends.
Cross-runtime
Section titled “Cross-runtime”Client verification is identical whether a TS Phaze action or a Rust #[get] handler produced the response — both emit X-Phaze-Sig over the body, and the browser verifies with the inlined public key. Rust signs via phaze::crypto::ed25519 / phaze::crypto::p256; the browser via Web Crypto. One wire format, two runtimes.