G-005 — Specify MWP Safely
Status: drafted → reviewed → accepted → closed.
Author: driver-claude
Date: 2026-05-06
1. The job of this gate
The original platform plan §3.2 named "MWP" but specified only three message types and no handshake transcript discipline. Per audits/manthan-platform-review.md §7-F2 and §8-F4: this allows replay, downgrade, transcript-splicing, and eclipse attacks.
G-005 produces a canonical MWP handshake spec with:
- Versioning that resists downgrade
- Mutual authentication with fresh nonces
- Transcript binding to prevent splicing
- Channel binding to TLS where available
- Concrete test vectors covering replay, downgrade, splicing
MWP is NOT used by the MVP wedge (G-001). The MVP uses HTTP. MWP is required only for Phase 3 (Controlled Gossip Test) and beyond. G-005 closes the design gate; implementation is Phase 3.
2. Wire format overview
MWP runs over WebSockets-over-TLS (wss://). Frames are binary, length-prefixed, CBOR-deterministic-encoded per [SPEC §3.5]. Each frame is a MwpFrame:
MwpFrame := {
"type": MwpFrameType,
"session_id": <bytes, 16 bytes>, # established at HELO
"seq": <uint64>, # monotonic per session, per direction
"payload": <Cbor> # frame-type-specific
}Where MwpFrameType ∈ { "HELO", "HELO_ACK", "PEER_EXCHANGE", "ENVELOPE_GOSSIP", "GET_ENVELOPES", "INVENTORY", "PING", "PONG", "BYE" }.
Frames are deterministically serialised. seq MUST be strictly monotonic. Out-of-order or repeated seq → connection drop.
3. The handshake
3.1 HELO (initiator → responder)
HELO.payload := {
"mwp_version": "1", # registry pinned; bump only on breaking
"herald_core_version": "0.2", # pin to envelope/SPEC version
"initiator_sigil": "<sigil:...>",
"initiator_nonce": <bytes, 16, CSPRNG>,
"supported_envelopes": [ "herald.envelope.v1", ... ], # registered domain tags this peer accepts
"supported_artifacts": [ "research_finding", ... ], # per G-007 typed artifacts
"advertised_roles": [ "agent", "conduit", "scribe", "herald" ], # role tags
"ct_policy": {
"log_ids_pinned": [ "<log_id_bytes>", ... ],
"min_sth_freshness_seconds": 86400
},
"max_frame_bytes": <uint, ≥ 65536, ≤ 1048576>,
"compress": "none" | "deflate-raw",
"tls_exporter": <bytes, 32, optional>, # RFC 5705 keying material if TLS endpoint exposes it
"ts": <Unix seconds>
}3.2 HELO_ACK (responder → initiator)
Mirror structure with the responder's sigil, fresh nonce, and intersection of supported sets:
HELO_ACK.payload := {
"mwp_version": "1",
"responder_sigil": "<sigil:...>",
"responder_nonce": <bytes, 16, CSPRNG>,
"agreed_envelopes": [ ... intersection of supported_envelopes ... ],
"agreed_artifacts": [ ... intersection ... ],
"agreed_roles": [ ... intersection of role tags both peers offer/want ... ],
"ct_policy": { responder's policy },
"max_frame_bytes": min(initiator's, responder's),
"compress": chosen,
"tls_exporter": <bytes, optional, must equal initiator's if both report it>,
"ts": <Unix seconds>,
"session_id": <bytes, 16 = SHA-256(transcript)[0..16]>, # see §3.4
"transcript_signature": {
"signer": "<responder_sigil>",
"alg": "ed25519",
"scope": "mwp_session_init",
"sig": <bytes, 64, Ed25519 sig over canonical transcript>
}
}3.3 HELO_ACK_2 (initiator → responder)
The initiator counter-signs the transcript:
HELO_ACK_2.payload := {
"session_id": <copied from HELO_ACK>,
"ts": <Unix seconds>,
"transcript_signature": {
"signer": "<initiator_sigil>",
"alg": "ed25519",
"scope": "mwp_session_init",
"sig": <bytes, 64, Ed25519 sig over the same canonical transcript>
}
}After HELO_ACK_2 is received and its signature verifies, the session is established. Any prior frame is treated as part of the handshake transcript; subsequent frames (PEER_EXCHANGE, ENVELOPE_GOSSIP, etc.) are session-bound.
3.4 The transcript
The transcript is the canonical concatenation:
transcript := dom_sep || cbor_canonical(HELO.payload)
|| cbor_canonical(HELO_ACK.payload_without_signature)where dom_sep := b"manthan.mwp.handshake.v1" (registered in [EXT §13] per G-008) and HELO_ACK.payload_without_signature is the HELO_ACK payload with the transcript_signature field stripped (just like envelope canonical body excludes its own signature).
Both peers compute the same transcript bytes. The session_id is SHA-256(transcript)[0..16] — content-addressed, mutually-derivable, used to bind subsequent frames.
3.5 What the handshake does NOT include
- No key exchange. MWP doesn't establish session-encryption keys; TLS handles that. MWP signs over
tls_exporter(RFC 5705) when available to bind the MWP session to the TLS session and detect MITM. - No nonce-based ratcheting. Forward secrecy is provided by TLS rekeying.
- No application-layer encryption — only signing/identity/integrity.
4. Anti-patterns the handshake defends against
4.1 Replay
- Session
session_idis fresh per HELO (derived from fresh CSPRNG nonces from both sides). - A replayed HELO_ACK from a prior session has a stale
session_idthat the initiator's HELO didn't cause; its transcript_signature won't verify against the new HELO's transcript.
Test vector R-1. Replay an old HELO_ACK against a new HELO → handshake fails on transcript-mismatch.
4.2 Downgrade
mwp_versionis signed in the transcript.- An attacker cannot rewrite
mwp_version: "2"tomwp_version: "1"without breaking the transcript signature. - The agreed lists (envelopes, artifacts, roles) are intersections of signed declared lists — there's no "weakest acceptable algo" parameter to attack.
Test vector R-2. Mid-flight rewrite of mwp_version → handshake fails.
4.3 Transcript splicing
- Transcript is
cbor_canonical(HELO) || cbor_canonical(HELO_ACK_without_sig)— order-preserved. - An attacker swapping HELO from session-A with HELO_ACK from session-B produces a transcript that neither peer signed.
Test vector R-3. Splice HELO_A + HELO_ACK_B → both transcript signatures fail (each peer signed a different transcript).
4.4 MITM
- If TLS provides
tls_exporter(RFC 5705 keying material), both peers include it. - An MITM that terminates two separate TLS connections sees two different exporter values, and cannot forge a HELO_ACK whose
tls_exportermatches what the initiator's TLS endpoint reports. - If TLS exporter is unavailable (older TLS, proxy in path), both peers must declare
tls_exporter: null. Verifiers MAY refuse such sessions for high-stakes envelopes.
Test vector R-4. MITM with separate TLS terminations → tls_exporter mismatch flags MITM.
4.5 Eclipse / peer-set capture
- HELO declares
advertised_roles. A peer claimingadvertised_roles: ["herald"]must be reachable as a Herald. - Per G-006 (R-215 gossip), the peer-discipline rules require ≥2 independent CT-log queries before trusting a peer's claimed role.
(Eclipse defence is mostly G-006's concern; MWP just makes the role declaration cryptographically verifiable.)
5. Test vectors required for Phase-3 conformance
A Phase-3 implementation MUST pass these vectors (tests/conformance/mwp/):
| ID | Scenario | Expected |
|---|---|---|
| MWP-V1 | Healthy handshake with both sigils having birth + endorsement | session established |
| MWP-V2 | HELO_ACK signed by wrong sigil | rejected at transcript_signature.signer != HELO_ACK.responder_sigil |
| MWP-V3 | Mid-handshake replay (above R-1) | rejected at transcript-mismatch |
| MWP-V4 | Downgrade attempt (above R-2) | rejected at transcript-mismatch |
| MWP-V5 | Transcript splicing (above R-3) | rejected on both sides |
| MWP-V6 | TLS exporter mismatch (above R-4) | rejected with "MITM-suspected" reason code |
| MWP-V7 | mwp_version not "1" | rejected with "unsupported-version" reason code |
| MWP-V8 | seq non-monotonic during data phase | connection dropped |
| MWP-V9 | Frame > max_frame_bytes | connection dropped |
| MWP-V10 | BYE frame | clean shutdown; both peers stop accepting frames in this session |
6. Domain tag registration
A new domain tag is required:
herald.mwp.handshake.v1 — MWP handshake transcript signaturesAdd to [EXT §13] domain-separation registry. This is a G-008 deliverable.
7. What this gate does NOT cover
- Implementation. Phase 3.
- TLS configuration / cert pinning. Deployment policy.
- Beyond v1. Future versions follow the existing protocol-versioning convention.
- Encryption-layer details. TLS does that.
8. Acceptance criteria
- [x] Frame format (this doc §2) is concrete and CBOR-canonicalisable.
- [x] Three-message handshake (HELO, HELO_ACK, HELO_ACK_2) defined with mutual signatures.
- [x] Transcript construction (§3.4) is deterministic and binds version, sigils, nonces, agreed parameters, and TLS exporter when available.
- [x] Five attack classes (replay, downgrade, splicing, MITM, eclipse) addressed with concrete defences.
- [x] Ten test vectors specified for Phase-3 conformance.
- [x] Domain tag registration (
herald.mwp.handshake.v1) flagged for G-008.
9. Sign-off
- [x] drafted → this document
- [x] reviewed → against [SPEC §3.4] multi-sig, [EXT §6] progressive signing, RFC 5705 (TLS exporter),
audits/systemic-risk-matrix.mdSR-002 (cartel cross-signing — informs eclipse defence) - [x] accepted → handshake is conformant with HERALD signature semantics; uses only Ed25519 + canonical CBOR; no novel crypto
- [x] closed → G-005 closed; implementation is Phase 3
— G-005 closed, 2026-05-06.